dxgx / blade-tailwind-extract
Extract Tailwind CSS classes to reduce Livewire wire transfer size
Requires
- php: ^8.1
- illuminate/console: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
README
A Laravel package that dramatically reduces Livewire component wire transfer sizes by extracting long Tailwind CSS class strings into short, reusable CSS class names. Perfect for applications with large lists of Livewire components.
The Problem
When using Livewire with Tailwind CSS, long class strings in Blade templates get transferred over the wire on every component update:
<div class="flex flex-col gap-2 p-4 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow"> <!-- Large payload repeated for every list item --> </div>
With 100 items in a list, this verbose markup significantly impacts performance.
The Solution
Blade Tailwind Extract extracts your Tailwind classes into a CSS file using @apply, replacing them with short class names:
Development Mode (Injected):
<div class="__card-wrapper__ flex flex-col gap-2 p-4 bg-white rounded-lg shadow-md __"> <!-- Easy to edit inline --> </div>
Production Mode (Extracted):
<div class="TW-a40f-card-wrapper"> <!-- Minimal wire payload --> </div>
Generated CSS:
.TW-a40f-card-wrapper { @apply flex flex-col gap-2 p-4 bg-white rounded-lg shadow-md; }
The a40f hash is derived from the file path, ensuring no conflicts between files.
Features
- ✅ Reduces Livewire wire payload - Short class names instead of verbose Tailwind strings
- ✅ Works with
class="",->class([...]), and@class([...])- Full Blade/Livewire support - ✅ Bidirectional - Extract for production, inject for development
- ✅ Safe - Reserved classes like
groupandpeerare automatically skipped - ✅ Collision-free - File-based hashing prevents class name conflicts
- ✅ Pattern matching - Process specific files or entire directories
- ✅ Configurable - Customize prefix, hash length, output path, and more
Installation
Install via Composer as a dev dependency (this is a development tool, not needed in production):
composer require dxgx/blade-tailwind-extract --dev
Publish the configuration file (optional):
php artisan vendor:publish --tag=blade-tailwind-extract-config
Usage
Extract Classes (Development → Production)
The target parameter accepts multiple formats:
0. No target (processes all files in search_path with confirmation):
# Prompts for confirmation before processing all .blade.php files php artisan dgtool:blade-tailwind-extract extract php artisan dgtool:blade-tailwind-extract e # Shows: # 1. First confirmation: "Are you sure you want to process X file(s)?" # 2. List of files to be processed (max 50 shown) # 3. Second confirmation: "Proceed with extract operation?" # Cancel at any prompt to abort
1. Directory (processes all .blade.php files recursively):
php artisan dgtool:blade-tailwind-extract extract ./resources/views php artisan dgtool:blade-tailwind-extract e ./resources/views/components
2. Single file path:
php artisan dgtool:blade-tailwind-extract extract resources/views/livewire/garage/list/item.blade.php php artisan dgtool:blade-tailwind-extract e components/card.blade.php
3. Pattern matching (searches in configured search_path):
php artisan dgtool:blade-tailwind-extract e *preview* php artisan dgtool:blade-tailwind-extract e *card*.blade.php php artisan dgtool:blade-tailwind-extract e list-item
4. Multiple targets (comma-separated, can mix patterns and files):
php artisan dgtool:blade-tailwind-extract e image-preview.blade.php,card-list.blade.php php artisan dgtool:blade-tailwind-extract e *preview*,*card*,header.blade.php
Inject Classes (Production → Development)
Restore Tailwind classes for editing (accepts same target formats as extract):
# No target (with confirmation prompts) php artisan dgtool:blade-tailwind-extract inject php artisan dgtool:blade-tailwind-extract r # 'r' is short alias for inject # Directory php artisan dgtool:blade-tailwind-extract inject ./resources/views # Pattern php artisan dgtool:blade-tailwind-extract r *preview* # Specific file php artisan dgtool:blade-tailwind-extract r components/card.blade.php # Multiple php artisan dgtool:blade-tailwind-extract inject *card*,*list*
Workflow
- Inject before editing (restore Tailwind classes to inline format)
- Edit your Blade files normally
- Extract after editing (compact back to short class names)
- Commit both Blade files and CSS file
Class Marker Syntax
Wrap classes you want to extract with double underscores:
<div class="__card-wrapper__ flex flex-col gap-2 p-4 __"> <!-- Content --> </div>
After extraction:
<div class="TW-a40f-card-wrapper"> <!-- Content --> </div>
Important: The group and peer Tailwind classes cannot be extracted (they break parent-child selectors). The tool will warn and skip them automatically.
Configuration
Configuration file: config/blade-tailwind-extract.php
return [ // CSS output file path 'css_output_path' => resource_path('css/tw-extracted.css'), // Class prefix (default: TW) 'class_prefix' => 'TW', // Hash length for file-based collision avoidance (default: 4) 'hash_length' => 4, // Default search path when using pattern matching 'search_path' => resource_path('views'), // Directories to ignore during file scanning 'ignored_directories' => [ './vendor/', 'vendor/', './node_modules/', 'node_modules/', ], // Reserved Tailwind classes that cannot be extracted 'reserved_classes' => [ 'group', 'peer', ], // Safety limit for extraction loops 'max_iterations' => 10, ];
Advanced Usage
Custom CSS Output Path
php artisan dgtool:blade-tailwind-extract extract ./resources/views --css-file=resources/css/my-custom.css
Working with Livewire Components
The tool works seamlessly with Livewire's ->class([...]) method and Blade's @class([...]) directive:
Before Extraction:
<div @class([ '__item-wrapper__ flex gap-2 p-4 __', 'bg-green-50' => $selected, ])>
After Extraction:
<div @class([ 'TW-a40f-item-wrapper', 'bg-green-50' => $selected, ])>
Handling Renamed/Moved Files
The hash is based on the file path. If you rename or move a Blade file:
- Inject the file first (restore classes)
- Move/rename the file
- Extract again (generates new hash)
How It Works
-
Extract Mode:
- Scans for
__name__ tailwind classes __patterns - Generates short class names:
{PREFIX}-{HASH}-{NAME} - Writes
@applyrules to the CSS file - Replaces inline classes with short names
- Scans for
-
Inject Mode:
- Reads existing
@applyrules from CSS file - Finds short class names in Blade files
- Restores original Tailwind class strings
- Reads existing
-
File Hash:
- Each file gets a unique 4-character hash (configurable)
- Prevents class name collisions between files
- Allows same semantic name (
card-wrapper) across different files
Gotchas & Best Practices
- ⚠️ Never manually edit
TW-*class names in extracted files - ⚠️ Always inject → edit → extract workflow
- ⚠️ Don't extract
group,peer, or other parent-modifier classes - ✅ Commit both Blade and CSS files together
- ✅ Run extract before deploying to production
- ✅ Run inject before starting development
Testing
composer test
Security
If you discover any security-related issues, please email packages@dxgx.dev instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.
Changelog
Please see CHANGELOG.md for recent changes.
Contributing
Please see CONTRIBUTING.md for details.