NPM 7 workspaces - how to install new package in workspace?

Viewed 23440

If I have a NPM 7 workspace like this:

root
   - submodule0
   - submodule1
   - submodule2

and I navigate to the submodule0 directory and run npm i somepackage it seems to "break" the workspace by creating a new package-lock.json in the submodule0 directory and installing all the dependencies there. In other words, it just does the old behavior that existed before I created the workspace. I was hoping for a command similar to lerna where I can install a new package in submodule0 from the root. Something like:

npm i somepackage --scope submodule0

So far, the only workaround I can find is to edit the submodule0 package.json and add the somepackage manually. Then run npm i from the root. Obviously this is not ideal because I need to look up the @latest version, navigate to the subdirectory, open the package.json, etc. etc. as opposed to just typing one line in the root.

6 Answers

Workspace support for npm install and npm uninstall was added in npm v7.14.0. You can now just do:

npm i somepackage --workspace=submodule0

Uninstalling modules has been the biggest pain, so this is really exciting. The npm team seems to be slowly adding support to commands one by one. Follow updates here: https://github.com/npm/cli/blob/latest/CHANGELOG.md.

I'm also baffled with why npm workspaces has been released without this functionality.

My current workaround uses the add-dependencies package, which adds dependencies to a declared package.json file, whilst skipping the installation process.

npm i add-dependencies -g

Then, from top level of the monorepo, you can run:

npx add-dependencies ./submodule0/package.json somepackage && npm i

Hopefully a --workspace argument will be added to npm i soon to avoid this faff.

Please refer to the answer of mattwad above if you have NPM v7.14.0 or above

Original answer

I wasn't quite happy with the suggestions, but combined all of them to use it in a npm script without any dependencies:

{
   "add": "npm install --package-lock-only --no-package-lock --prefix",
   "postadd": "npm install"
}

This can be used like following: npm run add -- submodule0 somepackage

Add only into package.json

U can use this to install package only into package.json ( you don't need external dependencies )

npm i --prefix packages/test --save --package-lock-only --no-package-lock express

followed by npm i to install specified dependency into mono repository root node_modules

Lerna

Also can use lerna to use workspace name to install dependency into

package.json

{
  "name": "mono",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
     "wsi": "function workspaceinstall() { ( scope=$1; shift; lerna exec --scope \"$scope\" -- npm install --package-lock-only --no-package-lock \"$@\") }; workspaceinstall"
  },
  "author": "",
  "license": "ISC",
  "workspaces": {
    "packages": [
      "packages/**"
    ]
  }
}

lerna.json

{
  "version": "1.0.0",
  "npmClient": "npm",
  "packages": ["packages/**"]
}

npm run wsi [workspace name] [dependency name to install]
npm run wsi @workspace/test express
npm run wsi @workspace/test express --save-prod 
npm run wsi @workspace/test @types/express --save-dev 

wsi script only modify package.json for provided workspace name, to actually install dependencies u have to run npm i

In my case, which is similar to yours, I deleted all dependencies from all the inner projects, deleted also the package-lock.json, and installed everything in the root.

/
  node_modules
  package.json >> all dependencies
  package-lock.json >> the only lock file that exists in the repo
  /packages
    /A
      package.json >> no dependencies
      -- no package-lock.json
    /B
      package.json >> no dependencies
      -- no package-lock.json
    /C
      package.json >> no dependencies
      -- no package-lock.json

This way, the node_modules folder ONLY resides on the root, and also the package-lock.json file is in the root.

If I allowed to have each project it's own package-lock.json I started seeing installation and runtime errors (because each project could have its own node_modules and its own version of a dependency).

This is the best way I see it works.

After trying to use the npm install with the --prefix --save --package-lock-only --no-package-lock options, npm always give the the error E404 - Not Found for my own packages of the monorepo that are not yet published to a registry. So even when trying to install external packages it fails because of my current dependencies in the package.json.

To workaround this issue I ended up with a mix of the previous suggestions:

"scripts": {
    "add": "add-dependencies $npm_config_scope/package.json",
    "postadd": "npm i",
},
"devDependencies": {
    "add-dependencies": "^1.1.0"
},

Then I can do:

npm run add --scope=packages/app express
npm run add --scope=packages/core eslint jest -D

This works fine for installing external packages. To install my own packages that lives inside the monorepo, I still have to manually edit the package.json, otherwise I get the package not found error.

Related