问题
TL;DR: Is there a way to have npm install
run automatically before running any npm script if your package.json
has been modified?
Problem Scenario
You pull or checkout a branch that updated package.json
. You run npm run my-script
. my-script
depends on a package that has newly been added to package.json
. my-script
fails. You wonder why. Before flipping over your desk you run npm install
just to be sure. my-script
runs successfully. You don't need a new desk.
I know that build / task runner tools like gradle
make sure that your dependencies are up-to-date before running a task. I has always been a (minor) pain point that npm
doesn't do it. I stumbled over two solutions that I don't particluarly like.
Non-Ideal Solution: make
Instead of relying on npm scripts in your package.json
to run commands you use make
and make use of its integrated dependency tracking with the following trick:
# Smart install: Only executes if package.json's
# modification date is later than node_module's
node_modules: package.json
npm install
@rm -f node_modules/.modified
@touch -m node_modules/.modified
install: node_modules
Source: https://mattandre.ws/2016/05/make-for-hipsters/
The problem is that you know have to rely on make
to run scripts and lose certain advantages of npm scripts such as conveniently referring to other scripts and running scripts in parallel (npm-run-all
). It's also harder to work with others if they don't know make
or have problems running it (Windows). It's an archaic tool outside of the node/npm ecosystem and too costly just for this smart install advantage.
Non-Ideal Solution: Git hook
Another way is to add a post-merge
git hook.
The problem is that this solution is local to the repository and can't be easily shared. npm install
will only be run automatically on git merges. When you change package.json
in any other way you still have to remember running npm install
. Admittedly, that's a minor point in practice. Nonetheless, it would be nice to never have to think about running npm install
at all when you want to run a script.
Source: https://davidwalsh.name/git-hook-npm-install-package-json-modified
Ideal Solution
I'd like to define my package.json
in a way similar to:
{
"scripts": {
"pre-run": "npm-smart-install",
"my-script": "…"
},
"dependencies": {
"npm-smart-install": "1.0.0"
}
}
npm-smart-install
is a hypothetical npm package that I wish existed. pre-run
is a hypothetical npm-scripts lifecycle hook. When I run npm run my-script
and package.json
has been modified since the last run of any script, run npm install
before running my-script
.
To repeat: Is there a way to have npm install
run automatically before running any npm script if your package.json
has been modified without relying on tools outside the npm ecosystem?
回答1:
Okay so I'm done with the package. Here it is. You can use it exactly the same way you specified in your ideal scenario. Just npm install install-changed
and add it to a custom script, like pre-run
in your example. It should figure out whether or not it needs to npm install
and does so if it needs to.
{
"scripts": {
"pre-run": "install-changed",
"my-script": "…"
},
You can also do this programatically but I don't think you're going to need this.
let installChanged = require('install-changed')
let isModified = installChanged.watchPackage()
The function above does exactly the same thing, additonally it also returns a boolean value which you might find useful.
回答2:
You can create a custom script that will run your smart install.
smart-install.sh file
#!/usr/bin/env bash
changedFiles="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"
checkForChangedFiles() {
echo "$changedFiles" | grep --quiet "$1" && eval "$2"
}
packageJsonHasChanged() {
echo "Changes to package.json detected, installing updates"
npm i
}
checkForChangedFiles package.json packageJsonHasChanged
Then if you have husky you can add that to the post-checkout hook or any hook you like. If you don't have husky, you can also add it directly to the scripts which essentially do the same thing.
.huskyrc file
{
"hooks": {
"post-checkout": "npm run smart-install"
}
}
package.json file
"scripts": {
...
"smart-install": "bash ./bin/smart-install.sh",
}
Either way it's a good idea to create a npm script to run smart-install
回答3:
Like other answers, but I think simpler because it's one line of shell script in package.json:
{
"scripts": {
"install-if-needed": "[ package.json -nt node_modules ] && npm install && touch node_modules",
"my-script": "npm run install-if-needed && ..."
}
}
or, basically equivalent:
{
"scripts": {
"install-if-needed": "[ package.json -nt node_modules ] && npm install && touch node_modules",
"premy-script": "npm run install-if-needed",
"my-script": "..."
}
}
You'll have to either inline npm run install-if-needed
or have a pre...
script for each script that needs it -- I don't know any other way to have it run before multiple other scripts.
Explanation: install-if-needed
checks the modification times on package.json
and node_modules
. If node_modules
is newer, it does nothing; otherwise it runs npm install
. The final touch node_modules
is necessary because npm install
may itself change the package.json modification time (if it is correcting whitespace in package.json for example).
来源:https://stackoverflow.com/questions/52466740/npm-install-if-package-json-was-modified