In Node.js projects, the package.json
file often serves as a convenient repository for our npm scripts. While one-liners within the scripts
section work perfectly fine for simple tasks, the situation becomes less ideal when scripts evolve into complex sequences. Think about scenarios involving multiple steps, conditional logic, robust error handling, or the need for comprehensive logging.
The problem with one-liners
Let's consider a common example: a deploy
script within your package.json
.
{
"name": "example-01",
"description": "A really long one-line deploy script."
"version": "1.0.0",
"scripts": {
"deploy": "rm -rf dist && mkdir dist && cp -r src/* dist && cd dist && zip -r ../my-project.zip * && cd .. && scp my-project.zip user@server:/path/to/deploy"
}
}
At first glance, this deploy
script seems to get the job done. However, it quickly becomes unwieldy. The entire process is crammed into a single line, making it a challenge to read, understand, and maintain.
The problem escalates when you try to introduce error checks and logging:
{
"name": "example-02",
"description": "Attempt to add error checks and logging to the one-line deploy script."
"version": "1.0.0",
"scripts": {
"deploy": "[ -d \"dist\" ] || (echo \"Error: 'dist' directory not found.\" && exit 1) && rm -rf dist && echo \"Removed dist directory\" || (echo \"Failed to remove dist directory\" && exit 1) && mkdir dist && echo \"Created dist directory\" || (echo \"Failed to create dist directory\" && exit 1) && cp -r src/* dist && echo \"Copied files to dist\" || (echo \"Failed to copy files to dist\" && exit 1) && cd dist && echo \"Changed directory to dist\" || (echo \"Failed to change directory to dist\" && exit 1) && zip -r ../my-project.zip * && echo \"Created zip archive\" || (echo \"Failed to create zip archive\" && exit 1) && cd .. && echo \"Changed directory back to project root\" || (echo \"Failed to change directory back to project root\" && exit 1) && scp my-project.zip user@server:/path/to/deploy && echo \"Deployed to server\" || (echo \"Failed to deploy to server\" && exit 1)"
}
}
Now, you're dealing with a visually cluttered script filled with escaped double quotes. Even worse, the exit 1
statements within the chained &&
commands won't actually halt the entire script's execution as intended. This creates a false sense of security and can lead to unexpected issues during deployment.
A cleaner solution: dedicated shell scripts
A straightforward and effective strategy for managing complex npm scripts is to move them out of your package.json
and into dedicated shell files. Let's refactor our deploy
script to demonstrate this approach:
1. Create a scripts
directory
If you don't already have one, create a scripts
directory within your project root.
2. Move the script into its own file
Cut the one-liner out of package.json
and create a new file named deploy.sh
inside the scripts
directory:
#!/bin/sh
# Check if the 'dist' directory exists
if [ ! -d "dist" ]; then
echo "Error: 'dist' directory not found."
exit 1
fi
# Remove the 'dist' directory
rm -rf dist
if [ $? -ne 0 ]; then
echo "Failed to remove dist directory"
exit 1
else
echo "Removed dist directory"
fi
# Create a new 'dist' directory
mkdir dist
if [ $? -ne 0 ]; then
echo "Failed to create dist directory"
exit 1
else
echo "Created dist directory"
fi
# Copy files from 'src' to 'dist'
cp -r src/* dist
if [ $? -ne 0 ]; then
echo "Failed to copy files to dist"
exit 1
else
echo "Copied files to dist"
fi
# Change directory to 'dist'
cd dist
if [ $? -ne 0 ]; then
echo "Failed to change directory to dist"
exit 1
else
echo "Changed directory to dist"
fi
# Create a zip archive of the 'dist' directory
zip -r ../my-project.zip *
if [ $? -ne 0 ]; then
echo "Failed to create zip archive"
exit 1
else
echo "Created zip archive"
fi
# Change directory back to the project root
cd ..
if [ $? -ne 0 ]; then
echo "Failed to change directory back to project root"
exit 1
else
echo "Changed directory back to project root"
fi
# Deploy the zip archive to the server
scp my-project.zip user@server:/path/to/deploy
if [ $? -ne 0 ]; then
echo "Failed to deploy to server"
exit 1
else
echo "Deployed to server"
fi
3. Update your package.json
Replace the full one-liner script with a call to execute your new deploy.sh
file.
{
"name": "my-react-app",
"version": "1.0.0",
"scripts": {
"deploy": "sh scripts/deploy.sh"
}
}
Benefits of this Approach
- Readability and Maintainability: The shell script is well-organized with proper indentation and comments, making it easier to read, understand, and modify in the future.
-
Robust Error Handling: The
exit 1
statements now function correctly, ensuring that the script stops immediately if an error occurs. -
Separation of Concerns: By keeping complex scripts in separate files, you maintain a cleaner
package.json
and promote better code organization within your project.
In Conclusion
While package.json
is a handy place for simple npm scripts, don't hesitate to leverage dedicated shell scripts for complex tasks. This approach enhances code clarity, maintainability, and error handling, contributing to a smoother development experience.
Top comments (1)
Just use Makefiles and ditch the npm script mess