If you're new to Node or have only worked on projects that already have their npm scripts set up, then you may be wondering what npm scripts
are and how they work. In this article, I'll give my best intuitive explanation for how/why they work and highlight some of the key tools available for writing simple npm scripts.
What Are These Scripts?
package.json
package.json
is the npm configuration file for a project, including its dependencies, project details, and scripts
. npm run
is the npm command that executes commands written in the scripts
section.
For my template article repo, I set up a linter script lint
, which is run with npm run lint
. Running prettier **/*.md
in the terminal directly wouldn't work because prettier
isn't found in the global PATH
.
"scripts": {
"lint": "prettier **/*.md"
},
"devDependencies": {
"prettier": "^2.1.2"
}
Meet PATH
PATH
is an environment variable that lists directories where the shell should look for commands. You can find a more thorough explanation on Linux Hint.
In order to find out what's inside your PATH
you can run echo $PATH
. Running that on a repl.it
sandbox, I got the following:
~/npm-scripting-tutorial$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
At least for our purposes, all npm run
does is append a few more directories onto the PATH
. We can confirm this by making the following npm script, and running it ourselves:
"scripts": {
"path": "echo $PATH",
"lint": "prettier **/*.md"
}
~/npm-scripting-tutorial$ npm run path
> @ path /home/runner/npm-scripting-tutorial
> echo $PATH
/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/home/runner/npm-scripting-tutorial/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
So npm run
prepended the following section to the PATH
:
/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/home/runner/npm-scripting-tutorial/node_modules/.bin
The /home/runner/npm-scripting-tutorial/node_modules/.bin
section is what gives us access to prettier
in the npm lint
command.
What Does This Mean?
The expansion of the PATH
is what allows us to call commands written in other npm packages without referencing their exact location in our node_modules
folder. If we didn't have this, our scripts would look more like the following:
"scripts": {
"lint": "./node_modules/.bin/prettier **/*.md"
}
That wouldn't be terrible, but it's not exactly ideal.
Other Highlights
Aliases
Some commands are so common that npm aliases them so they don't need to be prefixed by run
. These include:
So running npm start
is the same as running npm run start
.
Lifecycle Operations
There are some names that are hooks into steps of npm lifecycle commands (e.g. npm publish
, npm install
, npm start
). You can add scripts with these names to trigger commands at those steps:
"scripts": {
"build": "tsc --project .",
"prepack": "npm run build"
}
One unintuitive quirk with lifecycle scripts is that prepare
and prepublish
(both of which are now deprecated) also trigger on local npm install
, so if you have a build step that shouldn't trigger on install, it would be better associated with prepublishOnly
or prepack
.
The docs include more info on other lifecycle operations that you can hook into.
Command Arguments
Normally if we pass in a --option
to npm run
, it won't pass it through to the command written in scripts
. For instance, to make prettier
automatically fix issues, we would want to pass the --write
option. If we add a --
before the options, they will be passed through. This means we update our npm run lint
command above to the following to execute prettier --write
:
npm run lint -- --write
Conclusion
Hopefully this quick intro to the concepts around npm scripts makes it easier to read the scripts you encounter, as well as start writing your own. If you have any other questions, I recommend you look through npm's well-written documentation, starting with the npm CLI docs.
Top comments (1)
Hi. Interesting article. I'm studying npm
prepare
script and found your article.By the way,
prepublish
it's deprecated, butprepare
it's still supported.docs.npmjs.com/cli/v10/using-npm/s...