barryvdh/composer-cleanup-plugin
Composer plugin that cleans up your vendor directory by removing unnecessary files such as tests, documentation, and examples from installed packages, keeping your deployment lean and reducing disk usage without affecting runtime behavior.
What it is (and isn't). barryvdh/composer-cleanup-plugin is a Composer lifecycle plugin, not a Laravel package. There is no service provider, no config/ publish, no container bindings, no runtime footprint. It hooks Composer's post-package-install / post-package-update events and deletes dev-only files (tests, docs, examples, PHPUnit configs, CI metadata, .git) from each package after it is unpacked into vendor/. The correct TPM frame is build-artifact optimization, governed with the same rigor as a Dockerfile directive or a CI shell script — not as a platform dependency with a support contract.
Architecture fit: Weak but bounded. It touches exactly one phase of the delivery pipeline — deployment packaging — and no architectural layer of the application. It informs no product, roadmap, or user-facing decision. The entire business case reduces to one question: is there a measured, hard artifact-size constraint we are currently violating? Candidates are real but narrow: serverless (Lambda/Cloud Run) package ceilings, container image size and registry storage/egress billing, air-gapped or bandwidth-constrained distribution, and CI cache-size limits. Where composer install --no-dev --prefer-dist --optimize-autoloader plus a multi-stage build already clears the constraint, this plugin buys marginal bytes in exchange for install-time code execution.
Integration feasibility: Technically trivial, organizationally gated. It is a require-dev line in the root project, a config.allow-plugins entry, and an extra.cleanup.rules block (a * default with narrow per-package overrides). Three reviews — not engineering effort — determine feasibility:
*.md / docs/ rules strip LICENSE* and attribution text that SBOM or open-source obligations may require in the shipped artifact.allow-plugins allowlist.Technical risk: Medium-to-high and asymmetric — the downside is a broken or silently ineffective build, the upside is tens of megabytes. Dominant failure modes:
--no-plugins globally, which disables cleanup while every log line looks clean.--classmap-authoritative.path repositories / --prefer-source: cleanup writes through symlinks and mutates the actual local package checkout — a shared working copy, not a throwaway artifact.Note also what it does not save: the dist archives are still downloaded and cached in full, so install bandwidth and Packagist egress are unchanged. Savings are on-disk/in-artifact only.
Key questions:
LICENSE* and autoload roots can be protected?--no-dev, --prefer-dist, --optimize-autoloader, --classmap-authoritative, and --no-scripts (which does not disable plugins)?path repositories or symlinked packages anywhere in the build matrix?post-install-cmd / post-autoload-dump script touch files the plugin removes?vendor/ tree deterministic across clean installs and machines?composer install --no-plugins documented as the rollback lever?Stack fit: Laravel/PHP on Composer 2, confined to CI/CD image builds and release packaging. Composer loads plugins only from the root project, so adding it to a package we publish does nothing for consumers and creates confusion. Encode as policy: permitted in build pipelines as a pinned require-dev; prohibited in application require blocks; prohibited in published packages.
Migration path — evidence first, tooling second:
du -sh vendor, find vendor -type f | wc -l, container image size, release artifact size, build duration. No baseline, no pilot.du -sh vendor/*/* | sort -rh | head -20. On a modern Laravel app most dependencies already ship export-ignore for tests and docs, so expect a long tail in a handful of stragglers — not a jackpot.allow-plugins entry committed alongside.composer install --no-dev --prefer-dist --optimize-autoloader, then diff the vendor file manifest against baseline and confirm the delta matches expectation.Compatibility gates before promotion (all required):
LICENSE* and attribution files present (or explicitly excluded by policy sign-off).path repositories or --prefer-source in the matrix.*.Sequencing within the build: composer install --no-dev --prefer-dist → plugin runs per package → composer dump-autoload --optimize --classmap-authoritative → application build steps. Never run cleanup after autoload optimization without re-dumping, and never place a cleanup rule over a directory the classmap scanned.
Rollback: composer install --no-plugins, or revert composer.json/composer.lock and rebuild. Both must be in the runbook before the first production build ships.
Maintenance: Effectively zero upstream maintenance and non-zero internal maintenance — the worst combination to leave unowned. Assign a named platform/build owner at adoption and put a review date on the calendar. The realistic plan is a fork: budget a day to take custody and a few hours per year to rebase if Composer's plugin interface moves. Treat it as frozen, not as a supported dependency.
Support: No maintainer response, no security patch stream, no issue triage. If it breaks a build, we are the support desk. That is acceptable for a single scoped pipeline and unacceptable as an org-wide standard — an install-time plugin that can block composer install for every team simultaneously is a platform-wide single point of failure with no vendor.
Scaling: Scales fine mechanically — it adds seconds of file deletion per build and is cached inside Docker layers like any other composer install step. It scales poorly organizationally: every new service that copies the pattern inherits an archived dependency, a path-repository hazard, and a compliance question. Contain it with a documented template, a pinned version, and a written exception rather than allowing organic copy-paste adoption.
Failure modes to plan for:
| Failure | Symptom | Mitigation |
|---|---|---|
Missing allow-plugins entry |
Cleanup silently skipped, build green, no savings | CI assertion that cleanup ran; fail the job if the delta is zero on a known-bloated dependency |
CI passes --no-plugins globally |
Same silent no-op | Explicit flag audit of the pipeline |
| Over-broad rules | Missing license files, missing autoloaded fixtures, runtime fatals | Narrow per-package rules, LICENSE* exclusion, staging smoke deploy |
path repo / symlink write-through |
Local package checkout mutates | Prohibit cleanup on any pipeline with path repos |
--classmap-authoritative interaction |
Class-not-found at boot | Re-dump autoload post-cleanup; never rule over PSR-4 roots |
| Composer/PHP upgrade breakage | Install fails org-wide on the affected pipeline | Exact pin, committed lockfile, --no-plugins escape hatch, scheduled review |
| SBOM/audit variance | Audit tooling reports absent docs or license text | Legal stance documented before rules are written |
Ramp-up: Minimal for any engineer fluent in Composer — an hour to read the rules format, half a day for a competent build engineer to baseline, pilot, diff the manifest, and validate. The real ramp-up cost is organizational, not technical: getting security, legal, and platform to sign one exception, and getting the owner named. Budget roughly two engineer-days total for pilot plus validation plus runbook, and set an explicit exit criterion — replace with native flags, leaner base images, or a short deterministic CI cleanup script at the next review.
Bottom line for the TPM: do not adopt this as a standard dependency. Approve a time-boxed pilot only where a specific service is measurably over a hard artifact limit, with an exact version pin, a committed lockfile and allowlist entry, explicit LICENSE* protection, a manifest-diff and smoke-deploy gate in CI, a documented --no-plugins rollback, and a named owner with a scheduled re-review. If the delta doesn't move a real limit, ship the CI cleanup script or better Docker layering instead — same bytes, no install-time execution, nothing archived to depend on.
How can I help you explore Laravel packages today?