- Published on
Streamlining Your Development Workflow with npm
Scripts and npm-run-all
In software development, managing tasks and automating processes are essential. Two standout tools are npm-scripts and npm-run-all
. In this post, we’ll explore their capabilities and how npm-run-all
enhances npm-scripts. By combining these tools, you’ll optimize your workflow and boost productivity.
Understanding npm
Scripts
npm-scripts provide an elegant way to automate tasks and define custom scripts within your project’s package.json
file. These scripts can cover a wide range of activities, from running servers to testing code. npm-scripts shine with their simplicity and versatility, offering a unified approach to managing diverse development tasks.
To establish an npm-script, you need to define it within the scripts
section of your package.json
file. Each script is linked to a command executed when the script is invoked. Consider this example:
{
"scripts": {
"start": "node server.js",
"build": "webpack",
"test": "jest"
}
}
Here, three npm-scripts —start
, build
, and test
—are defined. Running npm run start
executes node server.js
, npm run build
triggers webpack
, and npm run test
invokes the jest
testing framework.
Note that npm run start
and npm run test
have shorthand versions, namely npm start
and npm test
, respectively.
Leveraging External Packages
npm-scripts also seamlessly incorporate external packages without requiring global dependencies. You can use locally installed packages by prefixing the command with npx
. For instance:
{
"scripts": {
"format": "npx prettier --write src/**/*.js"
}
}
Here, the format
script employs the prettier
package to format JavaScript files in the src/
directory.
Chaining and Combining Scripts
npm-scripts can go beyond single commands; they can chain and combine multiple commands using the &&
operator. This feature enables the creation of sophisticated workflows:
{
"scripts": {
"build": "npm run clean && webpack",
"clean": "rm -rf dist"
}
}
In this example, the build
script initiates the clean
script to remove the dist/
directory, then executes the webpack
build process.
Pre & Post Scripts
npm-scripts offer a mechanism to execute pre and post scripts. Pre scripts are executed before the script, and they are named using the convention pre[script-name]
. For instance, if your have a script named start
, you can create a pre script named prestart
. This is ideal for scenarios where you need to perform initial setup or configuration before your script runs.
Conversely, post scripts are executed upon the completion of a script. They adhere to the naming structure post[script-name]
. These scripts excel in performing cleanup tasks or generating post-execution reports.
Here’s an example:
{
"scripts": {
"clean": "rm -rf dist",
"test": "jest",
"prebuild": "npm run clean",
"build": "webpack",
"postbuild": "npm run test",
}
}
Run with arguments
It’s possible to pass arguments to npm run
using the following syntax:
npm run <command> [-- <args>]
Note the --
separator, is used to separate the params passed to npm
command itself, and the params passed to your script.
{
"scripts": {
"build": "webpack",
"watch": "npm run build -- --watch"
}
}
Environment Variables
npm-scripts run in an environment where many pieces of information are made available regarding the setup of npm
and the current state of the process.
Every entry in your
package.json
file becomes an environment variable. Thepackage.json
fields names are appended to thenpm_package_
prefix, separated by_
. For example for the followingpackage.json
file:{ "name": "test-package", "version": "1.0.0", "scripts": { "echo:package-name": "echo \"$npm_package_name\"", "echo:package-version": "echo \"$npm_package_version\"", } }
npm_package_name
retrieves the package name, i.e.,test-package
.npm_package_version
retrieves the package version, i.e.,1.0.0
.
You can use the
config
field to add your own custom key-value pairs:{ "config": { "port": 8080, }, "scripts": { "watch": "webpack --watch --port $npm_package_config_port" } }
You can also get:
- the npm version from
npm_config_npm_version
- the node version and operating system from
npm_config_user_agent
(e.g.,npm/9.8.1 node/v20.5.0 darwin arm64 workspaces/false
).
- the npm version from
You can run npm run env
to list all environment variables that will be available to your scripts at runtime.
Note
In Windows instead of$npm_package_name
you should use%npm_package_name%
to access the environment variables.
Arguments as Environment Variables
Another way of passing arguments is through environment variables. Any key-value pairs you add as an argument to your command will be translated into an environment variable with the npm_config_
prefix (e.g., --port=8080
will be converted into npm_config_port
environment variable). Meaning you can create a script like this:
{
"scripts": {
"watch": "webpack --watch --port $npm_config_port"
}
}
And then use it like so:
npm run watch --port=8080
Warning
Don’t confusenpm_config_
withnpm_package_config_
.
Enhancing with npm-run-all
While npm-scripts offer robust automation, certain scenarios demand running scripts in parallel or sequentially. This is where npm-run-all
shines, providing expanded capabilities to manage and execute npm-scripts.
Installing npm-run-all
To integrate npm-run-all
, start by installing it as a development dependency using:
npm install npm-run-all --save-dev
Running Sequentially
When scripts must execute sequentially, ensuring one completes before another starts, npm-run-all
comes to the rescue:
{
"scripts": {
"build": "npm-run-all build:assets build:minify",
"build:assets": "webpack",
"build:minify": "uglify-js"
}
}
Running in Parallel
Running scripts in parallel is a common need, especially when multiple services or processes need simultaneous initiation. With npm-run-all
, scripts can be executed in parallel using the --parallel
flag:
{
"scripts": {
"start": "npm-run-all --parallel start:backend start:frontend",
"start:backend": "node server.js",
"start:frontend": "webpack-dev-server"
}
}
Combining Parallel and Sequential Execution
npm-run-all
allows combining parallel and sequential execution in a single command, perfect for complex workflows:
{
"scripts": {
"deploy": "npm-run-all clean lint --parallel watch:html watch:css",
"clean": "rm -rf dist",
"lint": "eslint src",
"watch:html": "webpack --watch",
"watch:css": "sass --watch",
}
}
Glob-like pattern matching for script names
npm-run-all
allows glob-like patterns to specify npm-scripts names. The difference that the separator is :
instead of /
.
{
"scripts": {
"start": "npm-run-all --parallel start:*",
"start:backend": "node server.js",
"start:frontend": "webpack-dev-server",
"start:backend:db": "node db.js"
}
}
In this case, npm run start
runs all sub scripts of start: start:backend
and start:frontend
. However, it doesn’t execute sub-sub-scripts, like start:backend:db
. If you want to run both sub scripts and sub-sub scripts use **
:
{
"scripts": {
"start": "npm-run-all --parallel start:**",
"start:backend": "node server.js",
"start:frontend": "webpack-dev-server",
"start:backend:db": "node db.js"
}
}
Add arguments to scripts
To add arguments to a script name or pattern (e.g, build:**
) enclosed it in quest and add the arguments after --
:
{
"scripts": {
"start": "npm-run-all --parallel start:backend \"start:frontend -- --watch\"",
"start:backend": "node server.js",
"start:frontend": "webpack-dev-server"
}
}
Alternatively, placeholders can used to get the arguments passed to the npm run
command:
{
"scripts": {
"start": "npm-run-all --parallel start:backend \"start:frontend -- --port {1}\" --",
"start:backend": "node server.js",
"start:frontend": "webpack-dev-server"
}
}
And then use it like so:
$ npm run start 8080
> example@1.0.0 start /path/to/package.json
> npm-run-all build "start-server -- --port {1}" -- "8080"
npm-run-all
defines the following placeholders:
{1}
,{2}
, … - An argument.{1}
is the 1st argument,{2}
is the 2nd, and so forth.{@}
- All arguments.{*}
- All arguments as combined.
Other Useful Tools
Here are a couple of useful tools:
rimraf
allows you to runrm -rf
but is compatible with Windows.mkdirp
allows you to runmkdir -p
but is compatible with Windows.ncp
is a great cross-platform alternative tocp
.
Conclusion
By harnessing the power of npm-scripts and extending their capabilities with npm-run-all
, you can create a highly efficient development workflow. Automating tasks and orchestrating complex processes become remarkably easy. Simplify your workflow, boost productivity.