feat(critical-css): accept requiredClassNames and retries at config level - #1996
Merged
tomasmax merged 1 commit intoSep 23, 2026
Merged
Conversation
…s at config level
The README already documents both as config parameters, but they were only ever
read from a route: `const {requiredClassNames, retries} = pathOptions`. A route
declared as a plain string, which `createUrlFrom` explicitly supports, therefore
resolved both to `undefined`, and `if (!requiredClassNames) return css` skipped
the validation without a word. The only way to validate a route was to rewrite it
in object form and repeat the same list on every entry.
Both options are now resolved in one place: the route wins, the config is the
fallback, and a string route picks up the config values instead of nothing.
`??` rather than `||`, so `retries: 0` and an empty `requiredClassNames` stay
meaningful as a deliberate opt-out.
The stated default is corrected too. The README said 2 retries, the code has
always used 3; the number is now a named constant instead of a literal in a
parameter default.
13 specs, taking the server suite from 35 to 48. They also cover `createUrlFrom`,
which had none, since the string form of a route is what this change hinges on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomasmax
requested review from
andresin87,
andresz1,
ferransimon,
kikoruiz and
sui-bot
as code owners
September 23, 2026 10:52
maiderhernandorena-del
approved these changes
Sep 23, 2026
tomasmax
deleted the
feat/critical-css-config-level-required-class-names
branch
September 23, 2026 12:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
The README documents
requiredClassNamesandretriesas config parameters:They were never read from
config.extractCSSFromApponly ever took them off the route:Two consequences.
A route declared as a plain string is silently never validated.
createUrlFromexplicitly supports that form (typeof pathOptions === 'string' ? pathOptions : pathOptions.url), and the README's own first example uses it. Destructuring a string yieldsundefinedfor both, and thenif (!requiredClassNames) return cssreturns the first extraction with no check and no warning. So the shortest way to declare a route is also the one where the safety net quietly does not exist.The same list has to be repeated on every route.
requiredClassNamesis usually a per-app assertion ("the extraction is only valid if the design system's base classes made it in"), not a per-route one. Ours is identical across all 5 routes of each of our 2 tenants, which is the same array written 10 times, each of them a place to forget to update.The fix
One resolution point, route over config:
??and not||, soretries: 0on a route means "do not retry this one" instead of falling through to the config, andrequiredClassNames: []is a deliberate opt-out of a config-level list rather than an empty value to be replaced.Fully backwards compatible: with nothing set on
config, every route resolves exactly what it resolved before.The documented default was wrong
The README says the default is 2 retries. The code has always used 3 (
retries = 3in the signature ofextractCriticalCSS). The doc now says 3 and the number is a namedDEFAULT_RETRIESconstant instead of a literal buried in a parameter default. Changing the behaviour to match the doc would have silently cut an attempt from every consumer that relies on the default, so the doc is what moved.Tests
13 specs,
48 passingfor the server suite, up from 35.resolveRouteOptions: config applied to a string route, config applied to an object route, route overriding config, empty list as an opt-out, both undefined, the default of 3, config retries, route retries,retries: 0, and a missingpathOptions.createUrlFromhad no coverage at all and gets 3 specs, since the string form of a route is exactly what this change hinges on.Verified end to end, not just unit tested
The specs cover option resolution only;
extractCSSFromAppitself opens a browser, so it has none. Run against a static fixture with a layered stylesheet, two routes ('/home': '/'as a plain string,'/other': {url: '/other/'}), andrequiredClassNamesset onconfig.Output for the string route, which is the one that could not be validated before:
The at-rule context is intact: statements split and hoisted, the
@layer basewrapper kept, the@mediakept with its nested@layerinside, uncovered siblings dropped, the unlayered rule left unlayered. The second route drops the@mediablock, since.used-mobileis not on that page. Manifest and per-route files written as expected.Then the same run with
config.requiredClassNamesnaming a class that is not on either page, before and after the change:And with a class that is present, the output is byte-identical to the previous code on both routes and in
critical.json.Upgrade note
That second run is also the one behaviour change to be aware of. Anyone who followed the README and set
requiredClassNamesonconfighas been getting a no-op. After this, the validation is real, and a list that does not match the page ends with the attempts exhausted and an empty critical CSS written for every route, which is the documented "it would be discarded" behaviour finally taking effect. Worth checking the extraction log forAttempt limit reachedon the first build after upgrading.Note on versions
1.33.0was tagged and itsCHANGELOGwritten, but it never reached npm, sonpm view @s-ui/critical-css versionsdoes not list it. Merging this releases1.34.0, which carries the at-rule-context fix of #1995 as well.🤖 Generated with Claude Code