Using Composer to install npm and bower packages on production (i.e. no devDependencies)

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company
# Mastering Production Deployments: Installing NPM and Bower Packages Without Development Dependencies As senior developers managing large-scale applications, we constantly strive for efficiency, security, and clean deployment pipelines. One frequent sticking point when deploying PHP projects, especially those utilizing frontend asset management via Node Package Manager (NPM) and Bower, is ensuring that only necessary production dependencies are installed during the final rollout. This post addresses a common friction point: how to conditionally execute `npm install` and `bower install` within Composer's `post-install-cmd` without installing unnecessary `devDependencies` when running commands like `composer install --no-dev`. ## The Challenge with Post-Install Scripts You are absolutely right in identifying the limitation. When you define scripts within `composer.json`, such as the `post-install-cmd`, these commands execute unconditionally once Composer finishes its primary installation phase. There is no native mechanism within the Composer execution flow to inspect command-line flags (like `--no-dev`) and conditionally alter which subsequent system commands (`npm install` or `bower install`) are executed based on those flags. If we simply run: ```json "post-install-cmd": [ "npm install", "bower install" ] ``` Then running `composer install --no-dev` still triggers both commands, which inevitably pull in development dependencies unless the underlying package manager itself is configured to respect that flag (which is often not guaranteed across all toolchains). The desire for a cleaner separation—where production installs are lean and fast—is entirely valid. We want Laravel applications, for instance, to be deployed with minimal overhead, ensuring that only runtime necessities are present. ## The Solution: Externalizing Control via Custom Scripts Since Composer itself lacks this specific conditional logic for external package managers, the most robust and maintainable solution is to externalize the dependency management flow into a custom script or wrapper. Instead of relying solely on `post-install-cmd` for this complex orchestration, we introduce a dedicated deployment script that handles the environment context explicitly. ### Step 1: Refactor Composer Scripts We should simplify the `post-install-cmd` to handle only PHP-specific optimizations and remove the external package management calls entirely from this hook. This keeps Composer focused on PHP dependencies. **Refactored `composer.json` Example:** ```json { "scripts": { "post-install-cmd": [ "php artisan clear-compiled", "php artisan optimize" // Removed npm/bower calls from here ] }, // ... rest of composer.json } ``` ### Step 2: Implement a Deployment Wrapper Script We create a dedicated script (e.g., in `deploy.sh` or a custom `artisan` command) that checks the desired environment and executes the appropriate package manager commands with the correct flags. This gives us explicit control over what gets installed. **Example Shell Script (`install_production.sh`):** ```bash #!/bin/bash # Check if --no-dev flag was passed (or inferred from context) if [ "$1" == "--production" ]; then echo "Running production dependency installation..." # Install NPM dependencies, explicitly skipping devDependencies npm install --production # Install Bower dependencies, explicitly skipping devDependencies bower install --production else echo "Running full development dependency installation..." # Full install for local development or CI environments npm install bower install fi ``` ### Step 3: Orchestration Now, instead of relying on a single Composer command to handle everything, the deployment process becomes explicit. You run Composer for PHP setup, and then trigger the custom script for frontend assets: 1. **PHP Setup:** `composer install --no-dev` (This handles all PHP dependencies cleanly). 2. **Asset Setup:** `./install_production.sh --production` (This selectively installs only runtime Node/Bower packages). ## Conclusion: Clarity Over Magic While the initial thought might be to force this logic into Composer's hooks, true flexibility in deployment environments often requires separating concerns. As we see with modern application architecture—especially within frameworks like Laravel where dependency management is layered—it is often more effective to use Composer for its primary role (PHP dependencies) and utilize dedicated shell scripts for managing external toolchains like NPM and Bower. By adopting this wrapper approach, you gain explicit control over your deployment process, ensuring that production environments are lean, secure, and free from extraneous development packages. This practice promotes better separation of concerns, which is a core principle in building maintainable systems, much like the architectural clarity found in modern PHP frameworks like those promoted by Laravel company.