What is the most correct way to install npm packages
in nested sub folders?
my-app
/my-sub-module
package.json
package.json
What is the best way to have packages
in /my-sub-module
be installed automatically when npm install
run in my-app
?
If you want to run a single command to install npm packages in nested subfolders, you can run a script via npm
and main package.json
in your root directory. The script will visit every subdirectory and run npm install
.
Below is a .js
script that will achieve the desired result:
var fs = require('fs')
var resolve = require('path').resolve
var join = require('path').join
var cp = require('child_process')
var os = require('os')
// get library path
var lib = resolve(__dirname, '../lib/')
fs.readdirSync(lib)
.forEach(function (mod) {
var modPath = join(lib, mod)
// ensure path has package.json
if (!fs.existsSync(join(modPath, 'package.json'))) return
// npm binary based on OS
var npmCmd = os.platform().startsWith('win') ? 'npm.cmd' : 'npm'
// install folder
cp.spawn(npmCmd, ['i'], { env: process.env, cwd: modPath, stdio: 'inherit' })
})
Note that this is an example taken from a StrongLoop article that specifically addresses a modular node.js
project structure (including nested components and package.json
files).
As suggested, you could also achieve the same thing with a bash script.
EDIT: Made the code work in Windows
I prefer using post-install, if you know the names of the nested subdir. In package.json
:
"scripts": {
"postinstall": "cd nested_dir && npm install",
...
}
My solution is very similar.
Pure Node.js
The following script examines all subfolders (recursively) as long as they have package.json
and runs npm install
in each of them.
One can add exceptions to it: folders allowed not having package.json
. In the example below one such folder is "packages".
One can run it as a "preinstall" script.
const path = require('path')
const fs = require('fs')
const child_process = require('child_process')
const root = process.cwd()
npm_install_recursive(root)
// Since this script is intended to be run as a "preinstall" command,
// it will do `npm install` automatically inside the root folder in the end.
console.log('===================================================================')
console.log(`Performing "npm install" inside root folder`)
console.log('===================================================================')
// Recurses into a folder
function npm_install_recursive(folder)
{
const has_package_json = fs.existsSync(path.join(folder, 'package.json'))
// Abort if there's no `package.json` in this folder and it's not a "packages" folder
if (!has_package_json && path.basename(folder) !== 'packages')
{
return
}
// If there is `package.json` in this folder then perform `npm install`.
//
// Since this script is intended to be run as a "preinstall" command,
// skip the root folder, because it will be `npm install`ed in the end.
// Hence the `folder !== root` condition.
//
if (has_package_json && folder !== root)
{
console.log('===================================================================')
console.log(`Performing "npm install" inside ${folder === root ? 'root folder' : './' + path.relative(root, folder)}`)
console.log('===================================================================')
npm_install(folder)
}
// Recurse into subfolders
for (let subfolder of subfolders(folder))
{
npm_install_recursive(subfolder)
}
}
// Performs `npm install`
function npm_install(where)
{
child_process.execSync('npm install', { cwd: where, env: process.env, stdio: 'inherit' })
}
// Lists subfolders in a folder
function subfolders(folder)
{
return fs.readdirSync(folder)
.filter(subfolder => fs.statSync(path.join(folder, subfolder)).isDirectory())
.filter(subfolder => subfolder !== 'node_modules' && subfolder[0] !== '.')
.map(subfolder => path.join(folder, subfolder))
}
Adding Windows support to snozza's answer
var fs = require('fs')
var resolve = require('path').resolve
var join = require('path').join
var cp = require('child_process')
// get library path
var lib = resolve(__dirname, '../lib/')
fs.readdirSync(lib)
.forEach(function (mod) {
var modPath = join(lib, mod)
// ensure path has package.json
if (!fs.existsSync(join(modPath, 'package.json'))) return
// Determine OS and set command accordingly
const cmd = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
// install folder
cp.spawn(cmd, ['i'], { env: process.env, cwd: modPath, stdio: 'inherit' })
})
Just for reference in case people come across this question. You can now:
- Add a package.json to a subfolder
- Install this subfolder as reference-link in the main package.json:
npm install --save path/to/my/subfolder