Pin Vite's deduplication for an application-shaped entry

Plan step 37 review. The Vite test counted each of four files' plain
root rule, which the minifier folds into one even when a build repeats
the whole stylesheet: it passed against the Workbench's Tailwind entry,
which repeated 231 rules. It now asserts that no innermost rule repeats
under the same at-rules, which that build fails and a deduplicating one
passes, and it builds a second entry shaped like an application's —
outside the package, the foundation, then four component stylesheets
that each import button.css, and button.css again — where button.css
must also keep its first position. The fixture config refuses to run
without DEDUP_OUT_DIR, so it never writes into the package tree.

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 03:12:54 +02:00
co-authored by Claude Opus 5
parent 3854f4321c
commit 5e7c8b2b58
3 changed files with 109 additions and 38 deletions
+76 -25
View File
@@ -315,24 +315,72 @@ it('caches per file list and mtime, and resetCache() forces a fresh read', funct
});
/**
* The Vite deduplication `bundle()` is modelled on, pinned against the real bundler: a Vite build
* of `all.css` alone, no other plugin in the graph (tests/Fixtures/dedup.vite.config.mjs — its
* header explains why this is a separate, minimal entry rather than the Workbench's own).
* Every innermost rule of a minified stylesheet — `selector{declarations}` with no block inside —
* keyed by the at-rules around it (`@layer material.components{@media (hover:hover){…`), so the
* same rule written for two different conditions counts as two rules, not one repeated.
*
* The built CSS is minified, so rather than compare byte-for-byte against `bundle()`'s own
* (unminified) output, this counts one plain, unwrapped rule per file — `[data-md-<name>]{…}`
* exactly as button.css, icon.css, menu.css and list-item.css each write it before any `:not()`,
* `:is()` or `:where()` wrapping — and asserts each appears exactly once. A rule behind such
* wrapping is not used as a marker because Lightning CSS (Tailwind's minifier, which the
* Workbench's own build also carries) may re-split a single nested rule into several flat ones
* sharing a declaration block, which would inflate a literal count for a reason that has nothing
* to do with import deduplication; these four files' very first, plain, unwrapped selector is not
* affected by that and stays a reliable one-file-once fingerprint. They are also each imported
* from several other stylesheets (button.css: 11 places; icon.css and menu.css: a dozen more
* between them; list-item.css: list.css and carousel-item.css), so a broken dedup would double or
* triple the count rather than leave it at one.
* @return list<string>
*/
it('lands each of four widely-imported component stylesheets exactly once in a real Vite build', function () {
function stylesheetsBundleLeafRules(string $css): array
{
$leaves = [];
$stack = [];
$start = 0;
$length = strlen($css);
for ($i = 0; $i < $length; $i++) {
$char = $css[$i];
if ($char === '"' || $char === "'") {
$close = strpos($css, $char, $i + 1);
if ($close === false) {
break;
}
$i = $close;
} elseif ($char === '{') {
$stack[] = ['prelude' => trim(substr($css, $start, $i - $start)), 'open' => $i, 'leaf' => true];
if (count($stack) > 1) {
$stack[count($stack) - 2]['leaf'] = false;
}
$start = $i + 1;
} elseif ($char === '}') {
$block = array_pop($stack);
if ($block !== null && $block['leaf']) {
$context = implode('{', array_column($stack, 'prelude'));
$leaves[] = $context.'{'.$block['prelude'].'{'.substr($css, $block['open'] + 1, $i - $block['open'] - 1).'}';
}
$start = $i + 1;
} elseif ($char === ';' && ($stack === [] || ! $stack[count($stack) - 1]['leaf'])) {
// A statement between blocks (`@layer a,b;`) is not part of the next rule's selector.
$start = $i + 1;
}
}
return $leaves;
}
/**
* The Vite deduplication `bundle()` is modelled on, pinned against the real bundler
* (tests/Fixtures/dedup.vite.config.mjs, no plugin in the graph): `all.css`, and
* tests/Fixtures/dedup-app.css, an entry shaped like an application's — outside the package, the
* foundation, then split button, app bar, modal and pagination, which each import button.css, and
* button.css once more.
*
* The built CSS is minified, so this compares rules rather than files: no innermost rule may
* appear twice under the same at-rules. A build that inlined every occurrence of a shared
* stylesheet repeats hundreds of them (the Workbench's entry did while it shared one file with
* `@tailwindcss/vite`: button.css's hover and disabled rules thirteen times), while a file's plain
* root rule alone is no evidence — the minifier folds identical copies of that one together. In
* the application entry, button.css must also keep its first position: before split-button.css,
* the first file that imports it, not at the end where the application named it again.
*/
it('lands every rule once, at its first position, in a real Vite build of all.css and of an application entry', function () {
$node = config('livewire-material.node', 'node');
if (Process::run([$node, '--version'])->failed()) {
@@ -356,18 +404,21 @@ it('lands each of four widely-imported component stylesheets exactly once in a r
expect($result->successful())->toBeTrue($result->errorOutput());
$manifest = json_decode(File::get("{$outDir}/.vite/manifest.json"), true, flags: JSON_THROW_ON_ERROR);
$css = File::get($outDir.'/'.$manifest['resources/css/all.css']['file']);
foreach (['button', 'icon', 'menu', 'list-item'] as $name) {
$needle = "[data-md-{$name}]{";
$start = strpos($css, $needle);
foreach (['resources/css/all.css', 'tests/Fixtures/dedup-app.css'] as $entry) {
$css = File::get($outDir.'/'.$manifest[$entry]['file']);
$leaves = stylesheetsBundleLeafRules($css);
$repeated = array_keys(array_filter(array_count_values($leaves), fn (int $count): bool => $count > 1));
expect($start)->toBeInt("{$needle} should be in the built CSS");
$rule = substr($css, $start, strpos($css, '}', $start) - $start + 1);
expect(substr_count($css, $rule))->toBe(1, "{$name}.css's root rule should land once");
expect(count($leaves))->toBeGreaterThan(100, $entry)
->and($repeated)->toBe([], "{$entry} repeats a rule, so a shared stylesheet landed more than once");
}
$app = File::get($outDir.'/'.$manifest['tests/Fixtures/dedup-app.css']['file']);
expect(strpos($app, '[data-md-button]{'))->toBeInt()
->toBeLessThan((int) strpos($app, '[data-md-split-button]{'))
->and(strpos($app, '[data-md-app-bar]'))->toBeGreaterThan((int) strpos($app, '[data-md-split-button]{'));
} finally {
File::deleteDirectory($outDir);
}