Compile Class Transformer
The Compile Class Transformer optimizes your HTML by compiling groups of utility classes into single, deterministic class names, reducing HTML file size by up to 60%.
Overview
Instead of writing long lists of utility classes in your HTML:
<!-- Before: 150+ characters -->
<div class="flex items-center justify-between px-4 py-2 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow">
Content
</div>You can compile them into a single class:
<!-- After: ~20 characters -->
<div class="hw-2k9d3a">
Content
</div>How It Works
1. Mark Classes for Compilation
Add the :hw: trigger to mark utility groups for compilation:
<div class=":hw: flex items-center justify-between px-4 py-2 bg-white rounded-lg shadow-md">
Content
</div>2. Build with Compile Class Enabled
Configure the transformer in your config:
// headwind.config.ts
import type { HeadwindOptions } from 'headwind'
const config = {
content: ['./src/**/*.{html,js,ts,jsx,tsx}'],
output: './dist/headwind.css',
compileClass: {
enabled: true, // Enable the transformer
trigger: ':hw:', // Trigger string (default)
classPrefix: 'hw-', // Prefix for generated names (default)
},
} satisfies HeadwindOptions
export default config3. Build Your Project
Run the build command:
headwind buildThe transformer will:
- Scan Files - Find all classes marked with
:hw: - Generate Names - Create deterministic hashed class names
- Transform Files - Replace original classes with compiled names
- Generate CSS - Output CSS for both compiled and original utilities
4. Result
Your HTML is automatically transformed:
<!-- Your source file is updated automatically -->
<div class="hw-2k9d3a">
Content
</div>And CSS is generated for the compiled class:
.hw-2k9d3a {
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 1rem;
padding-right: 1rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
background-color: #ffffff;
border-radius: 0.5rem;
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
}Configuration
Enable/Disable
const config = {
compileClass: {
enabled: true, // Enable compile class transformer
},
} satisfies HeadwindOptionsCustom Trigger
Change the trigger string:
const config = {
compileClass: {
enabled: true,
trigger: ':compile:', // Use custom trigger
},
} satisfies HeadwindOptionsUsage:
<div class=":compile: flex items-center p-4">Content</div>Custom Prefix
Customize the generated class name prefix:
const config = {
compileClass: {
enabled: true,
classPrefix: 'c-', // Use 'c-' prefix instead of 'hw-'
},
} satisfies HeadwindOptionsGenerated classes: c-abc123, c-def456, etc.
Layer Configuration
Specify the CSS layer for compiled classes:
const config = {
compileClass: {
enabled: true,
layer: 'components', // Use 'components' layer
},
} satisfies HeadwindOptionsBenefits
1. Smaller HTML Files
Before:
<button class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200">
Click Me
</button>Character count: 285 characters
After:
<button class="hw-8k2j9s">
Click Me
</button>Character count: 49 characters
Savings: ~82% reduction
2. Better Caching
Compiled class names are deterministic - the same utilities always generate the same hash:
// These all generate the same compiled class name
<div class=":hw: flex items-center p-4">
<div class=":hw: p-4 flex items-center"> // Order doesn't matter
<div class=":hw: flex items-center p-4"> // Same hashBenefits:
- Browser cache reuses compiled classes across pages
- CDN caching is more effective
- Faster page loads
3. Automatic Deduplication
Identical utility groups share the same compiled class:
<!-- These both use hw-abc123 -->
<div class="hw-abc123">Card 1</div>
<div class="hw-abc123">Card 2</div>Only one CSS rule is generated:
.hw-abc123 {
/* utilities */
}Framework Integration
React / Next.js
// Component.tsx
export function Button({ children }: { children: React.ReactNode }) {
return (
<button className=":hw: bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded transition">
{children}
</button>
)
}After build:
// Component.tsx (automatically transformed)
export function Button({ children }: { children: React.ReactNode }) {
return (
<button className="hw-7k3m2p">
{children}
</button>
)
}Vue
<!-- Button.vue -->
<template>
<button class=":hw: bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded">
<slot />
</button>
</template>Svelte
<!-- Button.svelte -->
<button class=":hw: bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded">
<slot />
</button>Build Output
When compile class is enabled, you'll see statistics:
$ headwind build
🚀 Building CSS...
✅ Built 1243 classes in 8.45ms
📝 Output: ./dist/headwind.css
🔨 Compiled 15 class groups
📝 Transformed 8 files
📦 File size: 24.35 KB
📦 Compiled classes:
hw-2k9d3a ← flex items-center justify-between px-4 py-2 bg-white rounded-lg shadow-md
hw-8k2j9s ← inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md
hw-7k3m2p ← bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded
...Advanced Usage
Conditional Classes
Works with dynamic classes:
// React
<div className={`:hw: flex ${isActive ? 'bg-blue-500' : 'bg-gray-500'} p-4`}>
Content
</div>Note: Only the static parts (:hw: flex p-4) will be compiled. Dynamic parts remain as-is.
With Shortcuts
Compile classes work with shortcuts:
// Config
const config = {
shortcuts: {
'btn': 'px-4 py-2 rounded font-semibold transition-colors',
'btn-primary': 'btn bg-blue-500 text-white hover:bg-blue-600',
},
compileClass: {
enabled: true,
},
}Usage:
<button class=":hw: btn-primary">Click Me</button>
<!-- Compiles to -->
<button class="hw-k7m3n2">Click Me</button>Component Libraries
Create a compiled component library:
// components/Button.tsx
export const buttonClasses = {
primary: ':hw: bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded',
secondary: ':hw: bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded',
danger: ':hw: bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded',
}
// After build, these become:
// primary: 'hw-abc123'
// secondary: 'hw-def456'
// danger: 'hw-ghi789'Best Practices
1. Use for Repeated Patterns
✅ Good:
<!-- Card pattern used multiple times -->
<div class=":hw: rounded-lg shadow-md p-6 bg-white">Card 1</div>
<div class=":hw: rounded-lg shadow-md p-6 bg-white">Card 2</div>
<div class=":hw: rounded-lg shadow-md p-6 bg-white">Card 3</div>❌ Avoid:
<!-- One-off utility combinations -->
<div class=":hw: mt-4">Unique element</div>2. Group Related Utilities
✅ Good:
<div class=":hw: flex items-center justify-between">
<span class=":hw: text-lg font-bold text-gray-900">Title</span>
</div>❌ Avoid:
<div class=":hw: flex :hw: items-center :hw: justify-between">
Separate compilations
</div>3. Use with Component Classes
✅ Good:
// Define component styles once
function Card({ children }) {
return (
<div class=":hw: rounded-lg shadow-md p-6 bg-white hover:shadow-lg transition-shadow">
{children}
</div>
)
}4. Keep Dynamic Values Separate
✅ Good:
<div className={`:hw: flex items-center p-4 ${className}`}>
Static compiled + dynamic
</div>❌ Avoid:
<div className={`:hw: flex items-center p-4 ${dynamicPadding} ${dynamicBg}`}>
Too much dynamic content
</div>Performance Impact
Build Time
Compile class adds minimal overhead:
| Classes | Without Compile | With Compile | Overhead |
|---|---|---|---|
| 100 | 5ms | 6ms | +1ms |
| 1000 | 8ms | 10ms | +2ms |
| 10000 | 15ms | 19ms | +4ms |
Runtime Performance
Benefits:
- Smaller HTML = faster parsing
- Fewer class names = faster DOM operations
- Better gzip compression
Measurements:
- HTML size: ~60% reduction
- Parse time: ~15% faster
- Memory usage: ~10% lower
Troubleshooting
Classes Not Compiling
Check:
Transformer is enabled:
typescriptcompileClass: { enabled: true }Trigger is correct:
html<div class=":hw: flex items-center"> <!-- ✅ --> <div class="hw: flex items-center"> <!-- ❌ Wrong trigger -->Files are in content patterns:
typescriptcontent: ['./src/**/*.tsx'] // Must match your files
Wrong Hash Generated
Cause: Different utility order generates different hashes.
Solution: Utilities are automatically sorted before hashing:
<!-- Both generate the same hash -->
<div class=":hw: flex items-center p-4">
<div class=":hw: p-4 flex items-center">Source Files Not Transformed
Check:
- Build completed successfully
- Files have write permissions
- No syntax errors in files
Migration Guide
From Regular Classes
- <div class="flex items-center justify-between p-4 bg-white rounded shadow">
+ <div class=":hw: flex items-center justify-between p-4 bg-white rounded shadow">Run build:
headwind buildResult:
<div class="hw-abc123">Gradual Adoption
You can mix compiled and regular classes:
<!-- Compile repeated patterns -->
<div class=":hw: flex items-center p-4 bg-white rounded">
<!-- Use regular classes for one-offs -->
<span class="text-red-500">Error</span>
</div>Related
- Shortcuts - Combine with shortcuts for maximum effect
- Configuration - Full configuration options
- CLI - Build commands