Keep the application's stylesheets off the scaffold page too

Plan step 38 review: /material/shell still passed the whole configured
Vite list, so it loaded Tailwind and all.css a second time beside the
showcase bundle. Both frames now take ShowcaseAssetController::scripts():
every entry but a stylesheet, by the same extensions Laravel's Vite
treats as CSS (not only .css), and skip @vite() when nothing is left,
since a list of stylesheets alone would read a build manifest for
nothing. A test renders the overview, a section, a layout page and the
shell against a hot file. The frame's header said the rail collapses
from `lg`; it is `expanded` (840px). The config comment is shorter and
true.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-15 06:07:44 +02:00
co-authored by Claude Opus 5
parent 7abcc7c5f5
commit 2f649983fa
5 changed files with 71 additions and 25 deletions
+7 -10
View File
@@ -192,16 +192,13 @@ return [
| |
| Every component in every variant, rendered in the application's own | Every component in every variant, rendered in the application's own
| scheme. Off unless the application runs locally. 'vite' names the | scheme. Off unless the application runs locally. 'vite' names the
| application's own entry points: its JavaScript (Livewire, Alpine) and, | application's own entry points: its JavaScript (Livewire, Alpine) and
| for an application that still builds one of its own, a CSS entry. The | the CSS entry that imports this package's stylesheets. The showcase's
| showcase page draws its CSS only from its own bundle | pages pass @vite() only the entries that are not a stylesheet: their
| (ShowcaseAssetController serves Stylesheets::bundle() of all.css, | CSS is a bundle of their own (all.css, showcase.css and the scheme,
| showcase.css and the scheme) — nothing on it needs the application's | served by ShowcaseAssetController), so they need nothing from the
| build, Tailwind included — so the showcase's layout keeps only the | application's build. The error pages have no such bundle, so
| entries here that are not a stylesheet before passing the rest to | ErrorPage::assets() passes this whole list, CSS included, to @vite().
| @vite(), and links its own bundle after them. The error pages have no
| bundle of their own to fall back on, so ErrorPage::assets() still
| passes this whole list, CSS included, to @vite().
| |
*/ */
+8 -9
View File
@@ -1,5 +1,6 @@
{{-- The showcase's frame, and the package's own scaffold at work: the rail groups every section, {{-- The showcase's frame, and the package's own scaffold at work: the rail groups every section,
collapses and expands from `lg`, and opens as a modal from the app bar's menu button on a phone. collapses and expands from `expanded` (840px), and opens as a modal from the app bar's menu
button on a phone.
Pages move with wire:navigate, so the rail keeps its place and the theme stays. --}} Pages move with wire:navigate, so the rail keeps its place and the theme stays. --}}
@php @php
@@ -38,16 +39,14 @@
<x-livewire-material::theme-script /> <x-livewire-material::theme-script />
{{-- The application's own entries, JavaScript only (Livewire, Alpine): no showcase view {{-- The application's own entries without its stylesheets (ShowcaseAssetController::scripts()
needs anything Tailwind builds any more, and linking its CSS here would open the says why): the CSS of every showcase page is the bundle linked below, and nothing else. --}}
document's first @layer statement (Workbench package.css's header explains the trick) @if (($scripts = \NoNameWeb\LivewireMaterial\Http\Controllers\ShowcaseAssetController::scripts()) !== [])
ahead of the showcase's own bundle below, re-anchoring `material` before Tailwind's @vite($scripts)
own layers instead of after. config('livewire-material.showcase.vite')'s own comment @endif
says why ErrorPage::assets() still passes the whole array. --}}
@vite(array_filter(config('livewire-material.showcase.vite'), fn (string $entry): bool => ! str_ends_with($entry, '.css')))
@livewireStyles @livewireStyles
{{-- The showcase's own chrome: does not depend on the build above. --}} {{-- all.css, showcase.css and the application's scheme, without the application's build. --}}
<link rel="stylesheet" href="{{ \NoNameWeb\LivewireMaterial\Http\Controllers\ShowcaseAssetController::url() }}" /> <link rel="stylesheet" href="{{ \NoNameWeb\LivewireMaterial\Http\Controllers\ShowcaseAssetController::url() }}" />
</head> </head>
<body data-md-showcase> <body data-md-showcase>
+6 -6
View File
@@ -35,14 +35,14 @@
<x-livewire-material::theme-script /> <x-livewire-material::theme-script />
{{-- Kept only for the Livewire/Alpine runtime here this page has no Tailwind class of its {{-- As layout.blade.php: the application's entries without its stylesheets, then the
own. The next batches of step 38 leave it to the sections that still need it. It opens showcase's own bundle as the page's only CSS. --}}
the document's first @layer statement (Workbench package.css's header explains the @if (($scripts = \NoNameWeb\LivewireMaterial\Http\Controllers\ShowcaseAssetController::scripts()) !== [])
trick), which the link below must follow, not precede see layout.blade.php's comment. --}} @vite($scripts)
@vite(config('livewire-material.showcase.vite')) @endif
@livewireStyles @livewireStyles
{{-- The showcase's own chrome: does not depend on the build above. --}} {{-- all.css, showcase.css and the application's scheme, without the application's build. --}}
<link rel="stylesheet" href="{{ \NoNameWeb\LivewireMaterial\Http\Controllers\ShowcaseAssetController::url() }}" /> <link rel="stylesheet" href="{{ \NoNameWeb\LivewireMaterial\Http\Controllers\ShowcaseAssetController::url() }}" />
</head> </head>
<body data-md-showcase> <body data-md-showcase>
@@ -104,6 +104,25 @@ class ShowcaseAssetController
return route('livewire-material.stylesheet', ['hash' => self::hash(self::build())]); return route('livewire-material.stylesheet', ['hash' => self::hash(self::build())]);
} }
/**
* The application's Vite entries (`livewire-material.showcase.vite`) the showcase's own pages
* pass to `@vite()`: every one but a stylesheet — a path Laravel's Vite itself treats as CSS —
* since those pages draw their CSS from `url()`'s bundle alone. An application's CSS entry
* would load Tailwind for nothing, or the package's stylesheets a second time. Empty when the
* list names only stylesheets, and then the page skips `@vite()` altogether (it would read a
* build manifest for nothing). A JavaScript entry that imports a stylesheet itself still
* brings it: Vite links a chunk's own CSS.
*
* @return list<string>
*/
public static function scripts(): array
{
return array_values(array_filter(
(array) config('livewire-material.showcase.vite', []),
fn (mixed $entry): bool => is_string($entry) && preg_match('/\.(css|less|sass|scss|styl|stylus|pcss|postcss)(\?[^\.]*)?$/', $entry) !== 1,
));
}
protected static function build(): string protected static function build(): string
{ {
$files = [self::path('all.css'), self::path('showcase.css')]; $files = [self::path('all.css'), self::path('showcase.css')];
+31
View File
@@ -1,5 +1,6 @@
<?php <?php
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use NoNameWeb\LivewireMaterial\Http\Controllers\ShowcaseAssetController; use NoNameWeb\LivewireMaterial\Http\Controllers\ShowcaseAssetController;
use NoNameWeb\LivewireMaterial\Support\Stylesheets; use NoNameWeb\LivewireMaterial\Support\Stylesheets;
@@ -104,3 +105,33 @@ it('mounts none of the showcase asset routes when the showcase is off', function
$this->refreshApplication(); $this->refreshApplication();
} }
}); });
it('passes Vite the application entries but its stylesheets, on every kind of showcase page', function () {
$hot = sys_get_temp_dir().'/livewire-material-showcase-hot-'.Str::random(8);
file_put_contents($hot, 'http://vite.test');
Vite::useHotFile($hot);
config(['livewire-material.showcase.vite' => ['resources/css/app.css', 'resources/sass/extra.scss', 'resources/js/app.js']]);
try {
expect(ShowcaseAssetController::scripts())->toBe(['resources/js/app.js']);
foreach (['/material', '/material/progress', '/material/layout/feed', '/material/shell'] as $page) {
$this->get($page)
->assertOk()
->assertSee('http://vite.test/resources/js/app.js', false)
->assertDontSee('http://vite.test/resources/css/app.css', false)
->assertDontSee('http://vite.test/resources/sass/extra.scss', false)
->assertSee(ShowcaseAssetController::url(), false);
}
// A list of stylesheets alone leaves @vite() out, rather than reading a manifest for nothing.
config(['livewire-material.showcase.vite' => ['resources/css/app.css']]);
expect(ShowcaseAssetController::scripts())->toBe([]);
$this->get('/material/progress')->assertOk()->assertDontSee('http://vite.test', false);
} finally {
unlink($hot);
}
});