Inline the error layout's own rules beside the app's build
Plan step 46, from SealShare's breakpoint walk: an application imports the stylesheets its own views render, and none renders the error layout, so under a real build its 403/404/500 pages drew unstyled. With a build the page now keeps the app's Vite tags (foundation, tokens, scheme, font) and inlines Stylesheets::bundle() of components/error-page.css and its imports beside them (ErrorPage::layoutStyles(), cached). Without a build the fallback is unchanged. An application imports nothing for its error pages; one that imports all.css gets the same layered rules twice, which is harmless. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
05f115ab36
commit
70c983b7bb
@@ -10,19 +10,34 @@ use Throwable;
|
||||
/**
|
||||
* What the error layout needs from PHP, written so that nothing in it can fail the page: an
|
||||
* error page is shown precisely when something else already went wrong.
|
||||
*
|
||||
* The page carries its own component rules whatever the application imports. An application's
|
||||
* CSS entry imports the stylesheets of the components its own views render, and no view of its
|
||||
* renders this layout — the framework does, from the package's error-view root — so nothing ever
|
||||
* told an application to import `components/error-page.css`: `DesignGuard::missingStylesheets()`
|
||||
* reads the application's views, not the package's, and `unusedStylesheets()` would call such an
|
||||
* import unused. So an application imports nothing for its error pages: with a build the layout
|
||||
* takes the foundation, tokens, scheme and font from the application's Vite tags (`assets()`) and
|
||||
* inlines its own rules beside them (`layoutStyles()`); without one it inlines everything
|
||||
* (`fallbackStyles()`).
|
||||
*/
|
||||
class ErrorPage
|
||||
{
|
||||
/**
|
||||
* The error layout's own stylesheet, which imports button.css and shape.css (and through
|
||||
* button.css, icon.css, loading.css and tooltip.css) for what the layout renders.
|
||||
*/
|
||||
protected const string LAYOUT = 'components/error-page.css';
|
||||
|
||||
/**
|
||||
* The package stylesheets the fallback bundles: the foundation (every application needs it)
|
||||
* and the error layout's own stylesheet, which imports button.css and shape.css for it — the
|
||||
* same rules a Vite build would serve the page, `Stylesheets::bundle()` inlining them in PHP
|
||||
* instead. Not `all.css`: that would bundle every component's rules for a page that draws
|
||||
* three of them.
|
||||
* and the error layout's own stylesheet — the same rules a Vite build would serve the page,
|
||||
* `Stylesheets::bundle()` inlining them in PHP instead. Not `all.css`: that would bundle every
|
||||
* component's rules for a page that draws three of them.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected const array FILES = ['foundation.css', 'components/error-page.css'];
|
||||
protected const array FILES = ['foundation.css', self::LAYOUT];
|
||||
|
||||
/**
|
||||
* The scheme half of the fallback, cached per worker like `Stylesheets::bundle()`'s own cache:
|
||||
@@ -34,7 +49,9 @@ class ErrorPage
|
||||
protected static array $schemeCache = [];
|
||||
|
||||
/**
|
||||
* The application's Vite tags, or null when they cannot be made.
|
||||
* The application's Vite tags, or null when they cannot be made. They bring the foundation,
|
||||
* the tokens, the scheme and the font; the layout's own rules come from `layoutStyles()`
|
||||
* beside them, since the application's entry need not import them.
|
||||
*
|
||||
* A 500 during a deploy is the moment the manifest is missing, half-written or names files
|
||||
* that are not there yet — a missing manifest throws a ViteException, a truncated one a
|
||||
@@ -70,6 +87,25 @@ class ErrorPage
|
||||
return $previous === $current || $isHome ? null : $previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* The error layout's own rules for a page with its build: `Stylesheets::bundle()` of
|
||||
* `components/error-page.css` and the stylesheets it imports, inlined beside the application's
|
||||
* Vite tags, which bring everything else. `bundle()` caches it for the worker by its files'
|
||||
* mtimes, as it does the fallback's half.
|
||||
*
|
||||
* Nothing is dropped from it, because nothing in it needs a build: the package's only
|
||||
* `@font-face` is `tokens/font.css`'s, which only the foundation reaches and the application's
|
||||
* build serves, and no stylesheet the layout imports writes a `url()` a request would have to
|
||||
* resolve (tests/Feature/ErrorPagesTest.php checks both). An application whose entry also
|
||||
* imports this layout (through `all.css`, say) gets the same rules twice, in the same layers and
|
||||
* with the same declarations; the second copy decides nothing the first had not, so that is
|
||||
* harmless.
|
||||
*/
|
||||
public static function layoutStyles(): HtmlString
|
||||
{
|
||||
return new HtmlString(Stylesheets::bundle([self::path(self::LAYOUT)]));
|
||||
}
|
||||
|
||||
/**
|
||||
* A stylesheet for a page without its build: the foundation and the error layout's own rules —
|
||||
* `Stylesheets::bundle()` of `self::FILES`, the same inlining a Vite build does, so the fallback
|
||||
@@ -85,11 +121,19 @@ class ErrorPage
|
||||
*/
|
||||
public static function fallbackStyles(): HtmlString
|
||||
{
|
||||
$files = array_map(fn (string $file): string => dirname(__DIR__, 2)."/resources/css/{$file}", self::FILES);
|
||||
$files = array_map(self::path(...), self::FILES);
|
||||
|
||||
return new HtmlString(self::withoutFontFace(Stylesheets::bundle($files)).self::schemeStylesheet());
|
||||
}
|
||||
|
||||
/**
|
||||
* A package stylesheet's absolute path, from its path under `resources/css/`.
|
||||
*/
|
||||
protected static function path(string $file): string
|
||||
{
|
||||
return dirname(__DIR__, 2)."/resources/css/{$file}";
|
||||
}
|
||||
|
||||
/**
|
||||
* `$css` with every `@font-face` block dropped, wherever it sits: not a search for the block's
|
||||
* text (fragile the moment a comment or a value is reworded) but a structural read — find
|
||||
|
||||
Reference in New Issue
Block a user