Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Vendor Cleanup Laravel Package

barryvdh/laravel-vendor-cleanup

Removes unused or redundant files from your Laravel vendor directory, keeping it lean for deployment. Cleans up docs, tests, and other cruft from installed packages to reduce upload size and speed up deploys.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it where the command can actually run. It is a build-time tool, so treat it as a require-dev dependency:

composer require --dev barryvdh/laravel-vendor-cleanup
php artisan vendor:publish --tag=config   # or --provider="Barryvdh\VendorCleanup\VendorCleanupServiceProvider"

If the tag is wrong for your version, run php artisan vendor:publish with no arguments and pick the package's entry from the interactive list — that also tells you whether the config file is named vendor-cleanup.php and what keys it exposes.

Read the config before you run anything. The published file is the whole product: a pattern list (globs/regex for things like */tests/*, */docs/*, *.md, phpunit.xml*, .travis.yml) plus an exclusion list to protect specific packages or paths. Skim it end-to-end and note whether patterns are anchored — an unanchored *test* will eat src/Illuminate/Foundation/Testing and vendor/laravel/framework/src/Illuminate/*/Console/stubs if you are careless with edits.

Run dry first, always, and on a copy of reality. Establish a baseline:

du -sh vendor && find vendor -type f | wc -l
php artisan vendor:cleanup --dry-run | tee cleanup-preview.txt

Then measure the other side:

php artisan vendor:cleanup --no-interaction   # check --help for the non-interactive flag in your version
du -sh vendor && find vendor -type f | wc -l

First real use case: a Docker builder stage or a CI job — not your local working checkout. Running it locally means phpunit, artisan make:*, and anything that reads fixtures from vendor/ stop working until you rm -rf vendor && composer install (Composer generally will not re-extract packages it believes are intact). Keep your dev tree pristine.

Minimal working pipeline:

FROM php:8.3-cli AS build
# ...copy source, install composer...
RUN composer install --prefer-dist --no-interaction   # dev deps included so the command exists
RUN php artisan vendor:cleanup --no-interaction
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative
RUN php artisan package:discover --ansi

FROM php:8.3-cli AS runtime
COPY --from=build /app /app

Implementation Patterns

Post-install Composer hook (the "never forget" pattern). A manual run silently regresses the next time anyone runs composer install. Wire it in:

"scripts": {
  "post-install-cmd": ["@php artisan vendor:cleanup --no-interaction"],
  "post-update-cmd":  ["@php artisan vendor:cleanup --no-interaction"]
}

This only works when the package is installed. In a --no-dev build it will not be there — see Gotchas. If you need cleanup only in some environments, use --no-scripts on the install and invoke the step explicitly in the build script instead, so the trigger lives in one readable place.

Builder-stage-then-copy-forward (the correct answer for --no-dev pipelines). Install with dev dependencies in the builder, clean, then re-dump the autoloader without dev packages and carry the pruned tree into a slim runtime image. Everything downstream sees a normal, non-mutated-looking vendor/:

composer install --prefer-dist --no-interaction
php artisan vendor:cleanup --no-interaction
composer dump-autoload --optimize --no-dev --classmap-authoritative
php artisan package:discover
php artisan config:cache && php artisan route:cache && php artisan view:cache

Strict ordering. install → cleanup → dump-autoload → package:discover → config/route/view cache. package:discover reads every package's composer.json; classmap generation walks the file tree. Cleaning after either of those leaves you with an autoloader or manifest describing files that no longer exist.

Exclusion list as your incident-response lever. When a pruned build fails in staging, the fix should be a one-line config change, not a revert of the whole step:

// config/vendor-cleanup.php
'exclude' => [
    'vendor/acme/reports',          // reads its own templates at runtime
    'vendor/laravel/framework',     // stubs used by artisan make:*
],

Keep this list in version control next to the config and review it whenever someone reports a "works locally, not in prod" bug.

Dry-run as a CI diff, not a one-off. Emit the dry-run output as a build artifact on every pipeline. When someone bumps a dependency and the deletion list grows or shifts unexpectedly, you see it in the log instead of in an incident. Pair it with a size trend line: record du -sb vendor before and after and print both to the build summary. If the delta is not moving, stop running the step.

Validate against the pruned artifact, not the source tree. Build the image, run the full test suite inside it, then a smoke deploy. Local green means nothing here — the failure mode is always in the files you deleted.

Consider inlining instead of depending. The capability is roughly fifty lines of glob() over vendor/ plus a config array. If your org gates on bus factor or supply-chain review cost, copy the pattern list into scripts/cleanup-vendor.php, run it via @php, and own it. That is a legitimate outcome, not a defeat.

Gotchas and Tips

The --no-dev availability trap. The single most common integration failure: package is require-dev, the production build runs composer install --no-dev, and php artisan vendor:cleanup returns "command not found" — or worse, the post-install script fails the build with an opaque error. Solve with a builder stage (above) or by moving the package to require, and confirm the command exists in the stage where you invoke it.

You are mutating vendor/ after Composer recorded state. vendor/composer/installed.json and InstalledVersions no longer match disk. composer install will not re-extract packages it considers complete, so a partially-restored vendor cache, or a local run followed by a test run, produces confusing "file not found" errors that survive composer install. Recovery is rm -rf vendor && composer install. Never let a pruned vendor/ get baked into a Docker layer that a later stage expects to install into.

Never delete these: vendor/composer/* (including installed.json, autoload_*.php), each package's composer.json (package discovery and InstalledVersions read it), vendor/autoload.php, and Laravel's *.stub files under vendor/laravel/framework/src/Illuminate/*/Console/stubs/. Stripping stubs breaks artisan make:* at runtime — a classic "passes every test, blows up on the first scaffold" bug. Similarly, packages that ship config/route/migration stubs.

License files are not cleanup fodder. MIT, BSD, and Apache all require the copyright notice to accompany distributed copies. If your artifact ships to customers (on-prem installers, appliances, distributed Docker images), excluding LICENSE* from the pattern list is a legal decision, not a size optimization. Check with whoever owns compliance before enabling aggressive rules.

Any package that reads its own docs, fixtures, or tests at runtime breaks. This is dependency-graph-specific, not Laravel-version-specific — audit the heaviest dependencies rather than trusting a compatibility table. Typical offenders: packages globbing their own directory to discover providers or translation sources, test doubles used by production code, and anything with __DIR__.'/../tests' in a shipped file. Only a full test run and a smoke deploy against the pruned tree finds these.

Idempotent but not sticky. Running it twice is harmless; not running it after a composer update is the actual risk — deleted files reappear and your artifact quietly grows back. That is precisely why the step belongs in the build script, not in a wiki page.

Dry-run will not catch semantic breakage. It shows you what disappears, not what stops working. Never treat a clean dry-run as validation; it is a preview of blast radius.

Pin the version and watch it. One maintainer, ~161 stars, zero dependents. Maintainer reputation is real, but treat this as recipe-grade tooling: pin exact versions, re-validate after Composer or framework upgrades, and have a rollback that is literally "delete the step from the build script" — that is your entire failure path, so make sure it is available without an emergency revert.

Don't run it over a shared or symlinked vendor/. The command operates on real paths under the project root; a bind-mounted or symlinked vendor/ in a dev container can lead to deletions that persist outside the container and outlive the build.

Measure before you believe it. --no-dev, --prefer-dist, optimized autoloaders, and multistage builds usually dominate whatever this step returns. If nobody owns "how big is what we ship," the honest move is to add the size line to your build log first and revisit this package only when that number becomes someone's problem.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource