Npm - how to manage one-repo, multi-package codebase - javascript

I’m building a node.js app that needs to be scalable and maintainable. The idea is to have one repository, but with more than one module inside it.
We are using local modules with npm, but we have a problem is that when we update a modules npm won’t update the dependent services.
here an example of the package.json
// ./app/package.json
{
"dependencies": {
"error-lib": "file:../modules/error-lib"
},
"scripts": {
"start": "node app.js",
"debug": "node --inspect-brk app.js",
},
"main": "app.js",
}
// ../modules/error-lib/pacjake.json
{
"name": "error-lib",
"version": "1.0.0",
"main": "index.ts"
}
I have one problem when a developer updates the error-lib module, an npm install command won't update the old error-lib inside app/node_modules.
I don't want to bump the version of the modules on every change because they follow the same versioning and release lifecycle.
And I don't want to rm -rf modules manually every time.
Any idea on how to automate a repo with many local modules?
TL;DR (Why one repo, many local modules)
We came from a Java background, where we have a single repo multi-module architecture managed by maven.
What we do in maven is to isolate services, common-library, data model in isolated modules.
We have only one repo for all the modules and service since everyone follows the same release cycle with a uniform versioning.
When a developer needs to apply some change he or she can update one of the modules or the services and maven will take care of every dependency and the code will be up to date and ready to rock.
But we want separate modules for many reasons:
This will force us to decouple the functionality and make code reusable since every module can virtually be placed in a separate repo at any time.
If some module will become usable outside of the repo can be externalized and maintained.
We want to achieve the same design for a new part in node.js

Looks like you need $ npm link instead, creating symlinks to your packages within ./app/node_modules. This way, the linked packages are always 'up-to-date'.
Linking a package is a 2-step process:
$ npm link at ../modules/error-lib creates a symbolic package in your global node_modules folder under the name error-lib, specified in ../modules/error-lib/package.json its "name" value.
$ npm link error-lib at ./app to create the second link inside ./app/node_modules to <global-node_modules>/error-lib.
more about npm link
Edit in response to your comment:
I've seen some drawbacks of npm link:
the dependency is not listed in the app package.json, so how I know which dependency I'm linking?
the node_modules is not committed on the repo, so the CI/CD will need to do the linking while listing modules/error-lib inside the app package.json I have a place to take track of the dependencies.
There is any best practice on how to automate npm link or to mitigate the drawbacks?
NPM packages are supposed to be distributed through the npm registry. This means that your error-lib package also should be distributed this way, and be installed as any other package. This will require you to list your package in the package.json file in advance, which is essentially what you want.
If you instead intend to keep everything in one place there is no use in using NPM at all. Just resolve your packages with relative import paths in your software.
Since you said that all the packages follow the same release cycle as your main software, and they live in the same repo, why would you resolve these packages with NPM while they seem more like just part of the software?
error-lib is either a package, or it is not. If it is, it ideally has its own CI/CD pipeline, as large or tiny as required, involving testing, building and distribution. This pipeline is then to be run before the pipeline of your dependent repositories.

Related

Update package.json in several places after "npm i"

I want to be able to detect any new package installed according to my package.json file, so when ever I do "npm i", it will automatically added to another section on my package.json file.
For example, if I do "npm i axios", It will update in 2 places on my package.json file:
on "dependencies" as usual, and on new section I created: "extDependencies".
Is there any way to detect any new installed packages?
Check this out: npm-scripts documentation
If you want to run a specific script at a specific lifecycle event for
ALL packages, then you can use a hook script.
Place an executable file at node_modules/.hooks/{eventname}, and it’ll
get run for all packages when they are going through that point in the
package lifecycle for any packages installed in that root.
Hook scripts are run exactly the same way as package.json scripts.
That is, they are in a separate child process, with the env described
above.
You could use this to create a postinstall script (bash, python, node.js, etc) that reads the npm_package_name and npm_package_version environment variables and then use those to update the package.json.

How do you share local npm packages with other teams in a single repo?

Scenario
Consider the following folder structure:
/my-service
/src
/bin
/other-package
/src
package.json
package.json
There are 2 npm packages: one for my-service and one for other-package.
my-service depends on other-package.
my-service is owned by team A and other-package is owned by team B.
Keep in mind team B will still need to deploy my-service when other-package changes. This is why we are considering a shared repository.
Question
How do you structure this dependency such that the developer experience for both teams is reasonable?
Ideally, team B only needs to install and build other-package and can do everything they need to in that directory.
Likewise, team A should be able to use the latest version of other-package with minimal friction in local development.
Options
I am presently considering 2 options to solve this but do not know if there are better options out there.
Local file dependency
In this option, my-service has a package.json that looks like this:
"dependencies": {
"other-package": "./other-package"
},
"scripts": {
"build": "npm run build:other-package && npm run build",
"build:other-package": "cd other-package && npm i && npm run build && cd .."
}
Local tarball dependency
In this option, my-service has a package.json that looks like this:
"dependencies": {
"other-package": "./other-package/other-package-0.0.1.tgz"
}
In this case, team B needs to be responsible to run npm pack in the other-package directory when a new version is ready, update the package.json in my-service and commit that to git.
Put both packages into separate repositories, and in the package.json file of the first one put a dependency to the git repository of the second one. This looks like the following:
"dependencies": {
"my-other-package": "git://github.com/user/project.git#commit-ish"
}
For details on the available syntax options, see the npm documentation.
This way both teams can work on their packages individually, and independent of each other. At the same time it is possible to refer to the package via its git url, and you can even pin a specific branch, tag or commit by providing its commit-ish.
In case you use GitHub for managing your git repositories, you can also use a shortcut syntax:
"dependencies": {
"my-other-package": "user/project#commit-ish"
}
This also works for private repositories, but only for GitHub. In case of another git hosting solution, you need to use the syntax mentioned above.
Since you mentioned that the dependent team wants to use the latest version without too much friction, you might simply want to use something such as #master as commit-ish. Then on each npm install you get the latest version that is committed to the master branch.
Please note that this leads to non-reproducible builds, as you can't deterministically predict what will be installed. From my personal experience I'd rather recommend to pin your dependencies to a specific commit. While this is more work in maintenance, it leads to reproducable and predictable installs, which IMHO is worth a lot, but YMMV.
A big advantage of this approach is that both teams don't have to care about each other with respect to publishing or things like that, as you can depend on a package from the outside, without the team managing a package even knowing that someone else is using it.
If, for whatever reason, it absolutely necessarily HAS to be a single repository, and you can't use the approach I described, you might be interested in the term "monorepo". This basically is the concept of having a single (mono) repository for multiple packages. One tool to manage such a monorepo is Lerna, and you might want to have a closer look at it.
Those long divided shall be united; those long united shall be divided.
Recently I helped my team to transit to monorepo, merging 10+ repos into one. Monorepo has some downside, it puts pressure on version control facilities when it gets really big (like Google's). On the other hand, it facilitates code sharing, reuse build scripts, ci flows, batch op on git history and files, etc.
Typical monorepo approach treats subprojects equally.
/monorepo
/node_modules <-- shared top level node_modules
/packages
/my-service
/other-package
If not for particular reason, I suggest you keep both packages as siblings, instead of parent-child.
When done building either one, say other-package for example, you symlink the its dir into the top level node_modules dir.
# in /monorepo
ln -s packages/other-package node_modules
If you're willing to try out tool like lerna.js, it helps you do above chore.
Now you can specify your my-service/package.json like normal
"dependencies": {
"other-package": "latest"
}

How to verify an object instance? instanceof and ....prototype.isPrototypeOf(...) are not reliable [duplicate]

Whenever I make projects, I have to download all dependencies of node modules. Without copying the node_modules, Is there anyway to share the central node_modules in multiple projects?
like the followings, I have to run many commands every time..
npm install gulp-usemin
npm install gulp-wrap
npm install gulp-connect
npm install gulp-watch
npm install gulp-minify-css
npm install gulp-uglify
npm install gulp-concat
npm install gulp-less
npm install gulp-rename
npm install gulp-minify-html
You absolutely can share a node_modules directory amongst projects.
From node's documentation:
If the module identifier passed to require() is not a native module,
and does not begin with '/', '../', or './', then node starts at the
parent directory of the current module, and adds /node_modules, and
attempts to load the module from that location.
If it is not found there, then it moves to the parent directory, and
so on, until the root of the file system is reached.
For example, if the file at '/home/ry/projects/foo.js' called
require('bar.js'), then node would look in the following locations, in
this order:
/home/ry/projects/node_modules/bar.js /home/ry/node_modules/bar.js
/home/node_modules/bar.js /node_modules/bar.js
So just put a node_modules folder inside your projects directory and put in whatever modules you want. Just require them like normal. When node doesn't find a node_modules directory in your project folder, it will check the parent folder automatically. So make your directory structure like this:
-myProjects
--node_modules
--myproject1
---sub-project
--myproject2
So like this, even your sub-project's dependencies can draw on your main node_modules repository.
One drawback to doing it this way is you will have to build out your package.json file manually (unless someone knows a way to automate this with grunt or something). When you install your packages and add the --save arg to an npm install command it automatically appends it to the dependencies section or your package.json, which is convenient.
Try pnpm instead of npm.
pnpm uses hard links and symlinks to save one version of a module only ever once on a disk.
If you have npm installed, you can install in your terminal with:
npm install -g pnpm
To update your existing installations (and sub-directories) use:
pnpm recursive install
Or use the shorthand command (leave off -r if you need to target only one directory)
pnpm -r i
One helpful note: You may find some rare packages don't have all their dependencies defined. They might rely on the flat node_modules file directory structure of npm or yarn installs. If you run into issues of missing dependencies, use this command to hoist all the sub dependencies into a flat-file structure:
pnpm install --shamefully-hoist
It's best to avoid using the --shamefully-hoist flag as it defeats the purpose of using pnpm in the first place, so try using the command pnpm i your-missing-package first (See pnpm FAQ).
I found a trick, just take a look at the Symbolic Links (symlinks) on Windows or Linux, it is working just like shortcuts but more powerful.
Simply you need to make a Junction for your node_modules folder anywhere you want. The junction is nothing but a short cut to your original node_modules folder. Create it inside your project folder where the actual node_modules would have been created if used npm install.
To achieve this you need at least one node_modules real folder then make a Junction to it in the other projects.
On Windows, you can either use the Command Prompt, or use an application. Using the Command Prompt gives you a bit more control, using an application is easier I suggest Link Shell Extension.
Main directory should look like this
node_modules
Project 1
Project 2
Project 3
Project 4
just open the file Project 1/.angular-cli.json
change the schema
"$schema": "./node_modules/#angular/cli/lib/config/schema.json",
to
"$schema": "./../node_modules/#angular/cli/lib/config/schema.json"
and don't forget to create node_modules empty folder inside your project directory
See also npm v7.0.0's support for workspaces
RFC
https://github.com/npm/rfcs/blob/latest/implemented/0026-workspaces.md
Documentation
https://docs.npmjs.com/cli/v7/using-npm/workspaces
By looking at some articles it seems that Lerna
is a good tool for managing multiple projects inside a single directory (monorepo). It supports modules sharing without duplicating the entire packages in every folder and commands to install them in multiple projects.
Javascript monorepos
Monorepos by example
Building large scale apps in a monorepo
pnpm is also a simple and efficient tool, which doesn't duplicate those modules which are already installed for other projects.
Let's assume that having a single node_modules it should contain all the packages for all applications. thus your apps will also share most of the unique package.json entries (just the name should change)
my idea would be to have a single root and multiple src level as below
root\package.json
root\node_modules
root\\..
root\app1\src\\..
root\app2\src\\..
the only issue you might face would be having a backup of json (or tsconfig) for any app and restore them when you work on it or setup your startup scripts to serve any app

Node.js project with no package.json

Is it ok to have a node.js project with no package.json? The ones I see on the internet all come with package.json
What is the effect of having no package.json?
How is package.json created in the first place? Is it created automatically? I am wondering why I do not have package.json
Fundamentally, package.json is a meta file for your application. It lists all the configuration of your application.
What is the effect of having no package.json?
Nothing as far as you're running all your code locally and have no requirement for deployment whatsoever.
Let's setup a scene for you to understand this better.
Imagine that you wrote a brilliant application using node. Now all the chicks in your surrounding want it to play with. It is so fantastic!
Now you want to give it to them and during the development process you `npm install`ed so many things that your project grows beyond 4TB size.
There is no data storage device available to ship that huge code base.
Then the girl of your dream said I want it and I want it now. So you begin searching for app deployment process for node applications.
That is where you stumble upon a magical thing called package.json.
So what you do is you list all your npm installed modules under dependencies property. Then you delete node_modulesfolder, add package.json and commit the entire damn thing in github. Even the .zip file is of 10MB
Then she gets the code.
Types in npm install && npm start (which will install all the dependencies from the package.json` and start your application)
If you have package.json however, that is where you specify all your dependencies.
Using --save flag of npm install
Example.
npm install express --save
How is package.json created in the first place? Is it created automatically?
You can manually create a text file and save it as package.json
OR
A more sophisticated way is to use the command
npm init
I am wondering why I do not have package.json
Me too! :)
You're most probably following a tutorial that doesn't emphasize on initial configuration of the project OR the author of those tutorials presume that the reader has all the fundamentals down to begin with.
It is created automatically if you write npm init.
Then, every package you add using npm install packagename --save will be added to the dependencies list.
You need package.json so that when you want to use your project on another machine you don't have to copy all node_modules, but only your .js files you have written, assets and package.json. You can then run npm install command and it will automatically download and install all the required modules (found in the list of dependencies inside package.json).
You can also manually create or edit it, but it's easier to add --save when installing a module so you don't have to worry about package versions and stuff like that.
Also if you want to create a npm package, an open source project or stuff other people will use, it's either required or the norm to have this package.json file describing your project.
package.json is npm file, if you don't use npm you will not have this file, npm is a great tool if you want to use external libraries in your project but if you don't need it (which is very not likely unless you are doing something very simple), you don't need package.json file too.
To generate package.json file initialize npm in your project using npm init
possible reason thus it exist is you maybe you enter a wrong command like npm i -y, you must initialize the project first, just enter a command npm init -y
Welcome.
Well, if you are running it on your local machine, it's fine. now to answer your last question, package.json is not created automatically.
the npm command npm init -y creates the 'package.json' file. It basically makes sharing your code and installing your codebase easier.

Installing a local module using npm?

I have a downloaded module repo, I want to install it locally, not globally in another directory?
What is an easy way to do this?
you just provide one <folder> argument to npm install, argument should point toward the local folder instead of the package name:
npm install /path
From the npm-link documentation:
In the local module directory:
$ cd ./package-dir
$ npm link
In the directory of the project to use the module:
$ cd ./project-dir
$ npm link package-name
Or in one go using relative paths:
$ cd ./project-dir
$ npm link ../package-dir
This is equivalent to using two commands above under the hood.
Since asked and answered by the same person, I'll add a npm link as an alternative.
from docs:
This is handy for installing your own stuff, so that you can work on it and test it iteratively without having to continually rebuild.
cd ~/projects/node-bloggy # go into the dir of your main project
npm link ../node-redis # link the dir of your dependency
[Edit] As of NPM 2.0, you can declare local dependencies in package.json
"dependencies": {
"bar": "file:../foo/bar"
}
npm pack + package.json
This is what worked for me:
STEP 1: In module project, execute npm pack:
This will build a <package-name>-<version>.tar.gz file.
STEP 2: Move the file to the consumer project
Ideally you can put all such files in a tmp folder in your consumer-project root:
STEP 3: Refer it in your package.json:
"dependencies": {
"my-package": "file:/./tmp/my-package-1.3.3.tar.gz"
}
STEP 4: Install the packages:
npm install or npm i or yarn
Now, your package would be available in your consumer-project's node_modules folder.
Good Luck...
Neither of these approaches (npm link or package.json file dependency) work if the local module has peer dependencies that you only want to install in your project's scope.
For example:
/local/mymodule/package.json:
"name": "mymodule",
"peerDependencies":
{
"foo": "^2.5"
}
/dev/myproject/package.json:
"dependencies":
{
"mymodule": "file:/local/mymodule",
"foo": "^2.5"
}
In this scenario, npm sets up myproject's node_modules/ like this:
/dev/myproject/node_modules/
foo/
mymodule -> /local/mymodule
When node loads mymodule and it does require('foo'), node resolves the mymodule symlink, and then only looks in /local/mymodule/node_modules/ (and its ancestors) for foo, which it doen't find. Instead, we want node to look in /local/myproject/node_modules/, since that's where were running our project from, and where foo is installed.
So, we either need a way to tell node to not resolve this symlink when looking for foo, or we need a way to tell npm to install a copy of mymodule when the file dependency syntax is used in package.json. I haven't found a way to do either, unfortunately :(
Missing the main property?
As previous people have answered npm i --save ../location-of-your-packages-root-directory.
The ../location-of-your-packages-root-directory however must have two things in order for it to work.
package.json in that directory pointed towards
main property in the package.json must be set and working i.g. "main": "src/index.js", if the entry file for ../location-of-your-packages-root-directory is ../location-of-your-packages-root-directory/src/index.js
So I had a lot of problems with all of the solutions mentioned so far...
I have a local package that I want to always reference (rather than npm link) because it won't be used outside of this project (for now) and also won't be uploaded to an npm repository for wide use as of yet.
I also need it to work on Windows AND Unix, so sym-links aren't ideal.
Pointing to the tar.gz result of (npm package) works for the dependent npm package folder, however this causes issues with the npm cache if you want to update the package. It doesn't always pull in the new one from the referenced npm package when you update it, even if you blow away node_modules and re-do your npm-install for your main project.
so.. This is what worked well for me!
Main Project's Package.json File Snippet:
"name": "main-project-name",
"version": "0.0.0",
"scripts": {
"ng": "ng",
...
"preinstall": "cd ../some-npm-package-angular && npm install && npm run build"
},
"private": true,
"dependencies": {
...
"#com/some-npm-package-angular": "file:../some-npm-package-angular/dist",
...
}
This achieves 3 things:
Avoids the common error (at least with angular npm projects) "index.ts is not part of the compilation." - as it points to the built (dist) folder.
Adds a preinstall step to build the referenced npm client package to make sure the dist folder of our dependent package is built.
Avoids issues where referencing a tar.gz file locally may be cached by npm and not updated in the main project without lots of cleaning/troubleshooting/re-building/re-installing.
I hope this is clear, and helps someone out.
The tar.gz approach also sort of works..
npm install (file path) also sort of works.
This was all based off of a generated client from an openapi spec that we wanted to keep in a separate location (rather than using copy-pasta for individual files)
======
UPDATE:
======
There are additional errors with a regular development flow with the above solution, as npm's versioning scheme with local files is absolutely terrible. If your dependent package changes frequently, this whole scheme breaks because npm will cache your last version of the project and then blow up when the SHA hash doesn't match anymore with what was saved in your package-lock.json file, among other issues.
As a result, I recommend using the *.tgz approach with a version update for each change. This works by doing three things.
First:
For your dependent package, use the npm library "ng-packagr". This is automatically added to auto-generated client packages created by the angular-typescript code generator for OpenAPI 3.0.
As a result the project that I'm referencing has a "scripts" section within package.json that looks like this:
"scripts": {
"build": "ng-packagr -p ng-package.json",
"package": "npm install && npm run build && cd dist && npm pack"
},
And the project referencing this other project adds a pre-install step to make sure the dependent project is up to date and rebuilt before building itself:
"scripts": {
"preinstall": "npm run clean && cd ../some-npm-package-angular && npm run package"
},
Second
Reference the built tgz npm package from your main project!
"dependencies": {
"#com/some-npm-package-angular": "file:../some-npm-package-angular/dist/some-npm-package-angular-<packageVersion>.tgz",
...
}
Third
Update the dependent package's version EVERY TIME you update the dependent package. You'll also have to update the version in the main project.
If you do not do this, NPM will choke and use a cached version and explode when the SHA hash doesn't match. NPM versions file-based packages based on the filename changing. It won't check the package itself for an updated version in package.json, and the NPM team stated that they will not fix this, but people keep raising the issue: https://github.com/microsoft/WSL/issues/348
for now, just update the:
"version": "1.0.0-build5",
In the dependent package's package.json file, then update your reference to it in the main project to reference the new filename, ex:
"dependencies": {
"#com/some-npm-package-angular": "file:../some-npm-package-angular/dist/some-npm-package-angular-1.0.0-build5.tgz",
...
}
You get used to it. Just update the two package.json files - version then the ref to the new filename.
Hope that helps someone...
I came across different solution than above while installing custom build package for CKEditor5.
So I uploaded package to app root directory, than:
npm add file:./ckeditor5
In my package.json package is listed as a file:
"ckeditor5-custom-build": "file:ckeditor5",
I think this answer could be relevant to the topic on how to add local package.
For installing local module / package, that not yet on npm or you are developing an npm package and want to test it locally before publishing it. You can try this -
npm i yalc -g
Go to the module/package folder then -
yalc publish
Your packakge is ready to use, now go the project you want to install it -
yalc add <Your package name>
Package will be installed to you project. If you want to remove it -
yalc remove <Your package name>
For more recent versions of npm (I'm using 8.1.3 under macOS Big Sur), the sequence of commands is even easier...
cd /path-where-your-local-project-is/
npm init
This will ask you for some data related to your project and properly initialises your project.json file.
Once that is done, you can install additional modules with:
cd /path-where-your-local-project-is/
npm install --save-dev some-npm-module .
That's all you need!
Note: I believe that the trailing dot is not necessary if you're inside the project directory, but I also think that it doesn't hurt to add it :-)
(I wonder why the official docs still don't explain this...)

Categories

Resources