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

Skeleton Laravel Package

laravel-frontend-presets/skeleton

A starter skeleton for creating custom Laravel frontend presets. It provides the package structure and boilerplate needed to build and install your own preset, making it easier to replace Laravel’s default frontend scaffolding with a tailored setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a fork-and-rename template, not something you composer require into an app and expect behavior from. Its day-to-day job is to let a team turn repeated frontend scaffolding (Tailwind/Alpine config, webpack.mix.js, resources/*, base Blade layouts, auth views) into a single php artisan preset <name> command for Laravel 5.5–5.8 era projects.

Minimal path from zero to a working preset:

  1. Clone, don't install. Use GitHub's "Use this template" or git clone into a fresh internal repo (e.g., acme/frontend-preset).
  2. Rename everything mechanical:
    • composer.json → "name": "acme/frontend-preset", update "description", adjust extra.laravel.providers.
    • PSR-4 namespace under autoload.psr-4 (e.g., Acme\\FrontendPreset\\).
    • Rename src/SkeletonServiceProvider.php and src/SkeletonPreset.php and update the namespace/class inside each.
  3. Run composer install then composer dump-autoload. Confirm the class resolves before touching stubs.
  4. Replace stubs. The skeleton's stubs/ mirrors what a fresh Laravel app expects; swap in your package.json, webpack.mix.js, tailwind.config.js, resources/assets/*, and auth blade templates.
  5. Implement the preset class' install() method (see patterns below) — this is where the actual copying/deleting happens.
  6. Register the preset with Laravel's PresetCommand in the service provider's register() or boot().
  7. Validate against a throwaway laravel new app, never a real project with uncommitted work.

Where to look first:

  • README.md — the intended workflow and any example preset.
  • composer.json — package name, autoload, Laravel version constraint, and provider discovery block. If discovery is wrong, php artisan preset won't see your command.
  • src/ — the service provider is the entry point; the preset class is the payload.
  • stubs/ — the actual files your command will overwrite. Read every one before shipping.

First real use case: the team standardizes on Tailwind + Alpine with a shared config. Build the acme preset once, publish to an internal Composer registry, then every new project is composer require --dev acme/frontend-preset && php artisan preset acme. That collapses a half-day of manual setup into one command — which is only worth the fork if you're doing it three-plus times per quarter.

Implementation Patterns

Preserve the three-part split — ServiceProvider → PresetCommand registration → Preset class with install(). Resist the urge to collapse it; the split is what makes the preset discoverable and uninstallable.

Typical install() method:

public function install()
{
    // Copy the whole stub tree first, then delete what's stale.
    File::copyDirectory(__DIR__.'/../stubs/resources', resource_path());
    File::copyDirectory(__DIR__.'/../stubs/public', public_path());

    // Replace root-level config files that Laravel ships defaults for.
    copy(__DIR__.'/../stubs/package.json', base_path('package.json'));
    copy(__DIR__.'/../stubs/webpack.mix.js', base_path('webpack.mix.js'));

    // Remove the Sass/SCSS that your stack replaces with Tailwind.
    File::delete(resource_path('assets/sass/app.scss'));

    // Drop the default auth views so your Blade stubs take over cleanly.
    File::deleteDirectory(resource_path('views/auth'));
}

Service provider registration — the skeleton wires this via extra.laravel.providers in composer.json so Laravel auto-discovers it. The preset class is registered against the framework's preset command:

public function boot()
{
    if ($this->app->runningInConsole()) {
        PresetCommand::macro('acme', function ($command) {
            Preset::installAcme();
            $command->info('Acme preset installed. Run npm install && npm run dev.');
        });
    }
}

Local development loop. Don't tag and publish after every stub tweak. In a throwaway Laravel app's composer.json, add a path repository:

"repositories": [{ "type": "path", "url": "../acme-frontend-preset" }]

Then composer require --dev acme/frontend-preset:@dev. Symlinked, so edits in the preset repo are live.

Stub organization. Mirror the target app's directory layout inside stubs/ exactly (stubs/resources/assets/js/…, stubs/webpack.mix.js). Keeping the tree flat forces install() to grow hard-coded path maps that drift the moment Laravel changes directory conventions.

Multi-preset providers. If you ship acme-tailwind and acme-livewire from the same package, register two PresetCommand::macro() calls, each pointing to a distinct preset class. Keep each preset class single-purpose — sharing an install() with a $stack argument makes debugging half-applied presets painful.

Checkpoint before every test run. The preset has no rollback. Before php artisan preset acme in a test app, git add -A && git commit -m "pre-preset". Recovery is git reset --hard. Make that a habit in your own workflow so it becomes a habit in the README.

CI pattern for the fork itself. Spawn a fresh laravel new app in CI, composer require --dev your preset from the path repository, run php artisan preset acme, then assert npm run production exits 0. This is the only thing that catches stub drift after a framework upgrade.

Publishing. Tag 0.1.0 explicitly as internal-only. Do not submit to Packagist — you want the registry boundary to be a deliberate act for each consuming team, not an accidental composer require.

Gotchas and Tips

The preset command is destructive and silent. It overwrites package.json, webpack.mix.js, resources/assets/*, and auth views without confirmation and without a .bak. If someone runs it on a project with uncommitted work, that work is gone. Document git status must be clean before running as a hard precondition, and add a check in your install() that fails loudly if the working tree is dirty.

No rollback of any kind. A partial application (files copied, but composer install or npm install never completes) leaves a mixed-state repo. The only recovery is git reset --hard (loses everything) or a full re-clone. Bake the recovery step into the README: "If preset fails midway: git reset --hard && git clean -fd."

Provider discovery depends entirely on extra.laravel.providers being correct. If the command reports "preset not found" — or worse, "there are no commands defined in the preset namespace" — the first thing to check is that the fully-qualified provider class matches the actual namespace and file after your rename. Composer's dump-autoload after every rename.

PHP 8 / Composer 2 not verified. This is Laravel 5.5-era code. In practice you'll find array type hints without return types, no strict_types, and possibly File::copyDirectory return-value assumptions that quietly break under PHP 8's stricter error handling. Test explicitly against every PHP version your team will run; if you're on PHP 8, budget a fix-up commit before first use.

Command signature collision with laravel/ui. If laravel/ui is installed in the same app, its own preset command overwrites or conflicts with the framework's. You will see odd behavior — the app's preset command may ignore your macro entirely. Pick a distinct command name (e.g., artisan acme:install) if laravel/ui is in play, or enforce non-overlapping installs.

Hard-coded stub paths break on framework directory changes. The preset assumes resources/assets/, public/js/, and a specific auth view layout. When Laravel 5.6+ shuffled resources/assets/js → resources/js, presets that hard-coded the old path silently wrote to directories that no longer existed. Keep the stub tree mirroring the target app, and add a framework-version guard at the top of install():

if (version_compare($this->app->version(), '5.7', '>=')) {
    throw new \RuntimeException('This preset targets 5.5–5.6; use the acme/v2 preset.');
}

package.json handling is replace-not-merge. The preset typically overwrites the whole file, which means anything the app added (extra scripts, custom dependencies) is lost. If your team needs merge semantics, do the merge yourself in install() — read the app's package.json, decode, merge keys from the stub, re-encode. Same caveat for webpack.mix.js — a straight overwrite will drop any project-specific mix.* chains.

Deletion is more dangerous than copying. File::deleteDirectory(resource_path('views/auth')) is idempotent and fine, but File::delete() on a missing file is silent and on the wrong path is unrecoverable. Log every delete operation to the artisan output so the operator can see exactly what the preset removed.

Extension points to preserve. Keep the Preset class constructor taking no arguments and install() taking no arguments — some Laravel minor versions instantiate the preset reflectively. Keep the PresetCommand::macro() registration inside boot() and guard it with runningInConsole() so web requests never touch it. If you eventually want per-app configuration (e.g., brand colors), add a config/ file published via $this->publishes() rather than new constructor arguments.

The day-to-day reality. You will run this command a handful of times a year, not daily. The real day-to-day work is keeping the stubs in sync with your team's evolving stack — a new ESLint config, a Tailwind v3 → v4 migration, a Vite adoption. That maintenance happens in the preset repo, not in consuming apps, and it needs an owner and a release cadence. Treat the fork as a small internal product with a changelog and a versioned API (acme/frontend-preset v1 targets mix; v2 targets vite), or it will rot faster than the wiki it replaced.

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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core