Delete Tailwind's half and build the Workbench without it
Plan step 39 (parts 1-3, folded into one commit: PHP tests read the deleted files directly, so they cannot land apart from it). Tailwind leaves the whole stack: - Delete resources/css/tailwind.css, tokens/theme.css and tokens/utilities.css. Neither token file declared an --md-sys-* custom property of its own (both only referenced tokens declared elsewhere), so nothing loses a value; the md-* interaction and text classes already mirror utilities.css's declarations exactly (foundation/interaction.css, text.css). - npm uninstall tailwindcss @tailwindcss/vite; vite.config.js drops the plugin and its import; composer.json drops the tailwindcss keyword (no lock change — keywords are outside Composer's content hash). - The Workbench now builds one CSS entry, workbench/resources/css/app.css (all.css, showcase.css and the scheme; package.css is folded in and removed) instead of two, used by ErrorPage::assets() and every browser-test probe page's raw @vite() call; the showcase's own pages still take their CSS from the bundle route. - TokensTest and StylesheetsTest: the two facts theme.css and utilities.css carried (every scheme role becomes a colour, resolved on the element; md-type-* matches the type-* utilities) are asserted directly against the scheme and text.css now that there is no second copy to cross-check; StylesheetsTest gained a full-tree scan (every .css file under resources/css/ is reached from all.css or showcase.css, no exclusions left for Tailwind); the Workbench-entry test and every "moved out of tailwind.css" assertion updated for the single entry and its removal. - DesignGuard.php's comments and the development skill's setup section no longer name the deleted files or a second Tailwind entry. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
cfc57a9867
commit
f21943746e
@@ -10,14 +10,13 @@ use NoNameWeb\LivewireMaterial\Support\Layout;
|
||||
* would nest the layer twice); then its own `@layer material.<x>` blocks. The only rules outside
|
||||
* a layer are foundation/hidden.css's two `!important` ones.
|
||||
*
|
||||
* Every package component and layout stylesheet is plain CSS now (plan step 36), so the shape,
|
||||
* Tailwind-free and breakpoint checks below run over the whole tree `all.css` reaches (foundation,
|
||||
* layout and components together) rather than the foundation alone — everything but
|
||||
* `tailwind.css` and `tokens/theme.css`/`tokens/utilities.css`, which stay Tailwind until plan
|
||||
* step 39 and are simply never reached from `all.css`. A component's own values (colours as
|
||||
* roles, shadows as elevation levels, and so on) are each group's own concern, in
|
||||
* tests/Feature/Components/*StylesheetsTest.php; `bundle()`, the PHP bundler `all.css` is built
|
||||
* for, is tests/Feature/StylesheetsBundleTest.php's.
|
||||
* Every package component and layout stylesheet is plain CSS now (plan step 36), and Tailwind
|
||||
* itself is gone from the package (plan step 39), so the shape, Tailwind-free and breakpoint checks
|
||||
* below run over the whole tree `all.css` reaches (foundation, layout and components together)
|
||||
* rather than the foundation alone, and — one file it does not reach — `showcase.css`, checked on
|
||||
* its own further down. A component's own values (colours as roles, shadows as elevation levels,
|
||||
* and so on) are each group's own concern, in tests/Feature/Components/*StylesheetsTest.php;
|
||||
* `bundle()`, the PHP bundler `all.css` is built for, is tests/Feature/StylesheetsBundleTest.php's.
|
||||
*/
|
||||
const MATERIAL_LAYER_STATEMENT = '@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;';
|
||||
|
||||
@@ -178,9 +177,9 @@ function foundationStylesheets(): array
|
||||
|
||||
/**
|
||||
* Everything all.css reaches: the foundation, every layout stylesheet and every component
|
||||
* stylesheet, each once. What a check runs over now that every one of them is plain CSS (plan
|
||||
* step 36) — `tailwind.css` and its two token files are never in this tree, since all.css does
|
||||
* not import them.
|
||||
* stylesheet, each once. Every one of them is plain CSS (plan step 36), and Tailwind itself is
|
||||
* gone from the package (plan step 39) — `tailwindcss`, `tailwind.css` and its two token files no
|
||||
* longer exist, so there is nothing left for this tree to exclude.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
@@ -194,7 +193,7 @@ function stylesheetName(string $file): string
|
||||
return substr($file, strlen(stylesheetPath()) + 1);
|
||||
}
|
||||
|
||||
it('brings every foundation file and text.css into the foundation, and nothing of Tailwind', function () {
|
||||
it('brings every foundation file and text.css into the foundation, and nothing else', function () {
|
||||
$foundation = array_map(stylesheetName(...), foundationStylesheets());
|
||||
|
||||
$parts = collect(File::files(stylesheetPath('foundation')))
|
||||
@@ -207,11 +206,24 @@ it('brings every foundation file and text.css into the foundation, and nothing o
|
||||
->toBe(['reset.css', 'hidden.css', 'tokens.css', 'base.css', 'interaction.css', 'text.css'])
|
||||
->and($foundation)
|
||||
->toContain('tokens/scheme.css', 'tokens/shape.css', 'tokens/elevation.css', 'tokens/motion.css', 'tokens/type.css', 'tokens/state.css', 'tokens/spacing.css', 'tokens/font.css')
|
||||
// The Tailwind half: the theme, the token utilities and the components still written for it.
|
||||
->and(array_values(array_intersect($foundation, ['tokens/theme.css', 'tokens/utilities.css', 'tailwind.css'])))->toBe([])
|
||||
->and(array_values(preg_grep('/^components\//', $foundation)))->toBe([]);
|
||||
});
|
||||
|
||||
it('leaves no stylesheet under resources/css/ outside all.css or showcase.css', function () {
|
||||
// The full tree now that Tailwind is gone (plan step 39): every .css file on disk, not just the
|
||||
// ones a bundler happens to reach — a stray or orphaned file would show up here even though
|
||||
// nothing imports it, which the checks above (walking @import from all.css) cannot catch.
|
||||
$onDisk = collect(File::allFiles(stylesheetPath()))
|
||||
->filter(fn (SplFileInfo $file): bool => $file->getExtension() === 'css')
|
||||
->map(fn (SplFileInfo $file): string => stylesheetName((string) $file->getRealPath()))
|
||||
->sort()
|
||||
->values();
|
||||
|
||||
$reached = collect(allStylesheets())->map(stylesheetName(...))->push('showcase.css')->sort()->values();
|
||||
|
||||
expect($onDisk->all())->toBe($reached->all());
|
||||
});
|
||||
|
||||
it('opens every stylesheet all.css reaches with a header, the layer statement and plain imports', function () {
|
||||
foreach (allStylesheets() as $file) {
|
||||
$name = stylesheetName($file);
|
||||
@@ -548,17 +560,19 @@ it('hides an element below and from every breakpoint but compact, and nothing el
|
||||
expect($actual)->toBe($expected);
|
||||
});
|
||||
|
||||
it('gives every md-type class the declarations of its type utility', function () {
|
||||
preg_match_all('/@utility (type-[\w-]+) \{\n(.*?)\n\}/s', File::get(stylesheetPath('tokens/utilities.css')), $matches, PREG_SET_ORDER);
|
||||
it('gives every md-type class exactly font, letter-spacing and font-variation-settings', function () {
|
||||
// Tailwind's tokens/utilities.css used to declare the same 30 styles as `type-*` utility
|
||||
// blocks, and this test proved text.css's `.md-type-*` classes matched them; deleted with
|
||||
// Tailwind (plan step 39), text.css is the only copy left, so the shape it gives each class is
|
||||
// asserted directly rather than cross-checked against a second copy.
|
||||
preg_match_all('/\.md-(type-[\w-]+) \{\n(.*?)\n {4}\}/s', File::get(stylesheetPath('text.css')), $matches, PREG_SET_ORDER);
|
||||
|
||||
$utilities = collect($matches)->mapWithKeys(fn (array $match): array => ["md-{$match[1]}" => stylesheetDeclarations($match[2])]);
|
||||
$text = File::get(stylesheetPath('text.css'));
|
||||
$classes = collect($matches)->mapWithKeys(fn (array $match): array => ["md-{$match[1]}" => stylesheetDeclarations($match[2])]);
|
||||
|
||||
expect($utilities)->toHaveCount(30);
|
||||
expect($classes)->toHaveCount(30);
|
||||
|
||||
foreach ($utilities as $class => $declarations) {
|
||||
expect(stylesheetDeclarations(stylesheetBlock($text, ".{$class}")))->toBe($declarations, $class)
|
||||
->and(array_keys($declarations))->toBe(['font', 'letter-spacing', 'font-variation-settings']);
|
||||
foreach ($classes as $class => $declarations) {
|
||||
expect(array_keys($declarations))->toBe(['font', 'letter-spacing', 'font-variation-settings'], $class);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -611,57 +625,28 @@ it('ships exactly the documented text classes, each ink an M3 role', function ()
|
||||
}
|
||||
});
|
||||
|
||||
it('draws the interaction classes as the utilities the Tailwind components still use', function () {
|
||||
$utilities = File::get(stylesheetPath('tokens/utilities.css'));
|
||||
$interaction = File::get(stylesheetPath('foundation/interaction.css'));
|
||||
// A test used to prove foundation/interaction.css's `.md-*` classes matched tokens/utilities.css's
|
||||
// utility blocks byte for byte apart from two deliberate differences, because the components
|
||||
// still written in Tailwind read the utilities while every other component read the classes.
|
||||
// Deleted with Tailwind (plan step 39): every component reads the classes now, there is no second
|
||||
// copy left to drift from, and interaction.css's own StylesheetsTest coverage above (the layer it
|
||||
// writes into, no Tailwind directive) already holds it to the same shape every other foundation
|
||||
// file keeps to.
|
||||
|
||||
$normalise = fn (string $body): string => trim((string) preg_replace('/\s+/', ' ', $body));
|
||||
|
||||
foreach (['state-layer', 'focus-ring', 'touch-target', 'link'] as $name) {
|
||||
$class = stylesheetBlock($interaction, ".md-{$name}");
|
||||
|
||||
expect($normalise(str_replace(
|
||||
// The two deliberate differences: the data-md-* hook in place of the old one, and M3's
|
||||
// 48 CSS pixels for the target where the utility reads 3rem.
|
||||
['&[data-md-dragged]', 'var(--md-sys-measurement-space600)'],
|
||||
['&[data-dragged]', '3rem'],
|
||||
$class,
|
||||
)))->toBe($normalise(stylesheetBlock($utilities, "@utility {$name}")), $name);
|
||||
}
|
||||
|
||||
expect(stylesheetBlock($interaction, '.md-state-layer'))->toContain('&[data-md-dragged]::before')
|
||||
->not->toContain('[data-dragged]')
|
||||
->and(stylesheetBlock($interaction, '.md-touch-target'))->toContain('min-width: var(--md-sys-measurement-space600);');
|
||||
});
|
||||
|
||||
it('builds the package in a Workbench entry of its own, with the material layers between Tailwind\'s preflight and its utilities', function () {
|
||||
$package = stylesheetWithoutComments(File::get(__DIR__.'/../../workbench/resources/css/package.css'));
|
||||
it('builds the package in the Workbench\'s one CSS entry, with no Tailwind left to sit between', function () {
|
||||
$app = stylesheetWithoutComments(File::get(__DIR__.'/../../workbench/resources/css/app.css'));
|
||||
|
||||
// Both entries open by ordering the layers, so the order holds whichever the page links first:
|
||||
// above preflight, whose `* { padding: 0 }` would otherwise beat a component's padding, and
|
||||
// below the utilities.
|
||||
foreach (['package.css' => $package, 'app.css' => $app] as $name => $css) {
|
||||
expect(trim($css))->toStartWith('@layer properties, theme, base, material, components, utilities;', $name);
|
||||
}
|
||||
|
||||
// The package's CSS never shares an entry with Tailwind: `@tailwindcss/vite` inlines the
|
||||
// imports of an entry that uses Tailwind without deduplicating them, so every shared component
|
||||
// stylesheet would repeat later in the cascade instead of landing once, as it does for an
|
||||
// application (StylesheetsBundleTest.php pins that against a real Vite build).
|
||||
expect($package)->toContain("@import '../../../resources/css/all.css';")
|
||||
// Opens by ordering the layers, so the order holds whichever the page links first — an
|
||||
// application with Tailwind of its own still opens its own entry the same way (its header).
|
||||
expect(trim($app))->toStartWith(MATERIAL_LAYER_STATEMENT)
|
||||
->toContain("@import '../../../resources/css/all.css';")
|
||||
->toContain("@import '../../../resources/css/showcase.css';")
|
||||
->toContain("@import './material-scheme.css';")
|
||||
->not->toContain('tailwind')
|
||||
->not->toMatch('/@(?:source|theme|utility|variant|custom-variant|apply)\b/')
|
||||
->and($app)->toContain("@import 'tailwindcss'")
|
||||
->toContain("@import '../../../resources/css/tailwind.css';")
|
||||
->not->toContain('all.css')
|
||||
->not->toContain('foundation.css')
|
||||
->not->toContain('/components/')
|
||||
->not->toContain('material-scheme.css')
|
||||
->and(File::get(__DIR__.'/../../vite.config.js'))->toContain("'workbench/resources/css/package.css', 'workbench/resources/css/app.css'")
|
||||
->not->toMatch('/@(?:tailwind|source|theme|utility|variant|custom-variant|apply|config|plugin|reference)\b/')
|
||||
->and(File::get(__DIR__.'/../../vite.config.js'))
|
||||
->toContain("input: ['workbench/resources/css/app.css', 'workbench/resources/js/app.js']")
|
||||
->not->toContain('tailwindcss')
|
||||
->and(config('livewire-material.showcase.vite'))->toBe([
|
||||
'workbench/resources/css/package.css',
|
||||
'workbench/resources/css/app.css',
|
||||
'workbench/resources/js/app.js',
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user