@rakeyshgidwani/roger-ui-bank-theme-harvey 0.2.51 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/dist/components/ui/button.d.ts +3 -1
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/button.esm.js +3 -2
- package/dist/components/ui/button.js +3 -2
- package/dist/components/ui/layout/container.d.ts +57 -0
- package/dist/components/ui/layout/container.d.ts.map +1 -0
- package/dist/components/ui/layout/container.esm.js +173 -0
- package/dist/components/ui/layout/container.js +173 -0
- package/dist/components/ui/layout/index.d.ts +9 -0
- package/dist/components/ui/layout/index.d.ts.map +1 -0
- package/dist/components/ui/layout/index.esm.js +6 -0
- package/dist/components/ui/layout/index.js +6 -0
- package/dist/components/ui/layout/responsive-grid.d.ts +93 -0
- package/dist/components/ui/layout/responsive-grid.d.ts.map +1 -0
- package/dist/components/ui/layout/responsive-grid.esm.js +124 -0
- package/dist/components/ui/layout/responsive-grid.js +124 -0
- package/dist/components/ui/navigation/index.d.ts +2 -1
- package/dist/components/ui/navigation/index.d.ts.map +1 -1
- package/dist/components/ui/navigation/index.esm.js +1 -0
- package/dist/components/ui/navigation/index.js +1 -0
- package/dist/components/ui/navigation/progressive-navigation.d.ts +37 -0
- package/dist/components/ui/navigation/progressive-navigation.d.ts.map +1 -0
- package/dist/components/ui/navigation/progressive-navigation.esm.js +145 -0
- package/dist/components/ui/navigation/progressive-navigation.js +145 -0
- package/dist/components/ui/navigation/types.d.ts +21 -0
- package/dist/components/ui/navigation/types.d.ts.map +1 -1
- package/dist/components/ui/theme-toggle.esm.js +1 -1
- package/dist/components/ui/theme-toggle.js +1 -1
- package/dist/hooks/use-adaptive-layout.d.ts +2 -1
- package/dist/hooks/use-adaptive-layout.d.ts.map +1 -1
- package/dist/hooks/use-adaptive-layout.esm.js +13 -8
- package/dist/hooks/use-adaptive-layout.js +13 -8
- package/dist/hooks/use-device.d.ts +3 -1
- package/dist/hooks/use-device.d.ts.map +1 -1
- package/dist/hooks/use-device.esm.js +14 -7
- package/dist/hooks/use-device.js +14 -7
- package/dist/index.d.ts +19 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +9 -4
- package/dist/index.js +9 -4
- package/dist/plugins/css-purge-optimizer.d.ts +25 -0
- package/dist/plugins/css-purge-optimizer.d.ts.map +1 -0
- package/dist/plugins/css-purge-optimizer.esm.js +414 -0
- package/dist/plugins/css-purge-optimizer.js +414 -0
- package/dist/plugins/performance-monitor.d.ts +29 -0
- package/dist/plugins/performance-monitor.d.ts.map +1 -0
- package/dist/plugins/performance-monitor.esm.js +221 -0
- package/dist/plugins/performance-monitor.js +221 -0
- package/dist/plugins/progressive-css-loader.d.ts +21 -0
- package/dist/plugins/progressive-css-loader.d.ts.map +1 -0
- package/dist/plugins/progressive-css-loader.esm.js +227 -0
- package/dist/plugins/progressive-css-loader.js +227 -0
- package/dist/plugins/theme-css-generator.d.ts.map +1 -1
- package/dist/plugins/theme-css-generator.esm.js +19 -6
- package/dist/plugins/theme-css-generator.js +19 -6
- package/dist/styles.css +1025 -110
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.esm.js +4 -1
- package/dist/theme.js +4 -1
- package/dist/themes/phase1-constants.d.ts +23 -0
- package/dist/themes/phase1-constants.d.ts.map +1 -0
- package/dist/themes/phase1-constants.esm.js +180 -0
- package/dist/themes/phase1-constants.js +180 -0
- package/dist/themes/themes/default.d.ts.map +1 -1
- package/dist/themes/themes/default.esm.js +4 -1
- package/dist/themes/themes/default.js +4 -1
- package/dist/themes/themes/harvey.d.ts.map +1 -1
- package/dist/themes/themes/harvey.esm.js +4 -1
- package/dist/themes/themes/harvey.js +4 -1
- package/dist/themes/types.d.ts +62 -0
- package/dist/themes/types.d.ts.map +1 -1
- package/dist/themes/validation.d.ts +17 -0
- package/dist/themes/validation.d.ts.map +1 -1
- package/dist/themes/validation.esm.js +218 -0
- package/dist/themes/validation.js +218 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/progressive-css-injector.d.ts +80 -0
- package/dist/utils/progressive-css-injector.d.ts.map +1 -0
- package/dist/utils/progressive-css-injector.esm.js +217 -0
- package/dist/utils/progressive-css-injector.js +217 -0
- package/package.json +1 -1
- package/src/components/ui/button.tsx +9 -6
- package/src/components/ui/layout/container.tsx +312 -0
- package/src/components/ui/layout/index.ts +10 -0
- package/src/components/ui/layout/responsive-grid.tsx +286 -0
- package/src/components/ui/navigation/index.ts +2 -0
- package/src/components/ui/navigation/progressive-navigation.tsx +453 -0
- package/src/components/ui/navigation/types.ts +41 -0
- package/src/components/ui/theme-toggle.tsx +4 -4
- package/src/hooks/use-adaptive-layout.ts +13 -9
- package/src/hooks/use-device.tsx +17 -10
- package/src/index.ts +19 -4
- package/src/plugins/css-purge-optimizer.ts +491 -0
- package/src/plugins/performance-monitor.ts +292 -0
- package/src/plugins/progressive-css-loader.ts +269 -0
- package/src/plugins/theme-css-generator.ts +22 -6
- package/src/styles/components/base/badge.css +2 -2
- package/src/styles/components/base/button.css +238 -35
- package/src/styles/components/base/card.css +2 -2
- package/src/styles/components/base/checkbox.css +3 -3
- package/src/styles/components/base/label.css +3 -3
- package/src/styles/components/feedback/skeleton.css +1 -1
- package/src/styles/components/feedback/toast.css +1 -1
- package/src/styles/components/index.css +3 -0
- package/src/styles/components/layout/container.css +466 -0
- package/src/styles/components/layout/index.css +5 -0
- package/src/styles/components/layout/responsive-grid.css +422 -0
- package/src/styles/components/navigation/breadcrumb.css +1 -1
- package/src/styles/components/navigation/index.css +1 -0
- package/src/styles/components/navigation/menu.css +2 -2
- package/src/styles/components/navigation/pagination.css +4 -4
- package/src/styles/components/navigation/progressive-navigation.css +633 -0
- package/src/styles/components/navigation/sidebar.css +4 -4
- package/src/styles/components/navigation/stepper.css +2 -2
- package/src/styles/components/navigation/tabs.css +1 -1
- package/src/styles/progressive.css +17 -0
- package/src/styles/themes/harvey.css +103 -19
- package/src/styles/utilities/semantic-input-system.css +7 -13
- package/src/theme.ts +5 -1
- package/src/themes/phase1-constants.ts +189 -0
- package/src/themes/themes/default.ts +5 -1
- package/src/themes/themes/harvey.ts +5 -1
- package/src/themes/types.ts +77 -1
- package/src/themes/validation.ts +249 -0
- package/src/types.ts +77 -1
- package/src/utils/progressive-css-injector.ts +254 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS Purge Optimizer Plugin
|
|
3
|
+
* Implements Phase 3 Performance Optimization: CSS Bundle Optimization
|
|
4
|
+
*
|
|
5
|
+
* Optimizations:
|
|
6
|
+
* - Removes unused responsive breakpoint styles
|
|
7
|
+
* - Purges unused CSS custom properties
|
|
8
|
+
* - Optimizes CSS layer ordering
|
|
9
|
+
* - Minifies and compresses output
|
|
10
|
+
*/
|
|
11
|
+
import postcss from 'postcss';
|
|
12
|
+
const DEFAULT_CONFIG = {
|
|
13
|
+
breakpoints: ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'],
|
|
14
|
+
preserveCustomProperties: [
|
|
15
|
+
// Core design system variables
|
|
16
|
+
'--cs-*',
|
|
17
|
+
'--tw-*', // Preserve all Tailwind CSS variables
|
|
18
|
+
// Component design tokens (critical for responsive system)
|
|
19
|
+
'--button-*',
|
|
20
|
+
'--badge-*',
|
|
21
|
+
'--card-*',
|
|
22
|
+
'--input-*',
|
|
23
|
+
'--label-*',
|
|
24
|
+
'--checkbox-*',
|
|
25
|
+
'--menu-*',
|
|
26
|
+
'--sidebar-*',
|
|
27
|
+
'--breadcrumb-*',
|
|
28
|
+
'--pagination-*',
|
|
29
|
+
'--stepper-*',
|
|
30
|
+
'--tabs-*',
|
|
31
|
+
'--progressive-nav-*',
|
|
32
|
+
'--skeleton-*',
|
|
33
|
+
'--toast-*',
|
|
34
|
+
'--popover-*',
|
|
35
|
+
'--tooltip-*',
|
|
36
|
+
'--enterprise-*',
|
|
37
|
+
'--font-*',
|
|
38
|
+
'--user-menu-*',
|
|
39
|
+
'--subscription-*',
|
|
40
|
+
'--list-*',
|
|
41
|
+
'--tree-*',
|
|
42
|
+
// Layout and utility tokens
|
|
43
|
+
'--container-*',
|
|
44
|
+
'--grid-*',
|
|
45
|
+
'--spacing-*',
|
|
46
|
+
'--breakpoint-*'
|
|
47
|
+
],
|
|
48
|
+
preserveSelectors: [
|
|
49
|
+
'.sr-only',
|
|
50
|
+
'.focus\\:*',
|
|
51
|
+
'.hover\\:*',
|
|
52
|
+
'.active\\:*',
|
|
53
|
+
'[data-*]'
|
|
54
|
+
],
|
|
55
|
+
aggressive: false,
|
|
56
|
+
minify: true
|
|
57
|
+
};
|
|
58
|
+
export default function cssPurgeOptimizer(config = {}) {
|
|
59
|
+
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
60
|
+
return {
|
|
61
|
+
name: 'css-purge-optimizer',
|
|
62
|
+
apply: 'build',
|
|
63
|
+
generateBundle(_options, bundle) {
|
|
64
|
+
// Find CSS assets in bundle
|
|
65
|
+
const cssAssets = Object.keys(bundle).filter(key => key.endsWith('.css'));
|
|
66
|
+
cssAssets.forEach(assetKey => {
|
|
67
|
+
const asset = bundle[assetKey];
|
|
68
|
+
if (asset.type === 'asset' && typeof asset.source === 'string') {
|
|
69
|
+
const optimizedCSS = optimizeCSS(asset.source, finalConfig);
|
|
70
|
+
// Calculate optimization savings
|
|
71
|
+
const originalSize = Buffer.byteLength(asset.source, 'utf8');
|
|
72
|
+
const optimizedSize = Buffer.byteLength(optimizedCSS, 'utf8');
|
|
73
|
+
const savings = originalSize - optimizedSize;
|
|
74
|
+
const savingsPercent = Math.round((savings / originalSize) * 100);
|
|
75
|
+
console.log(`🔧 CSS Optimization: ${assetKey}`);
|
|
76
|
+
console.log(` Original: ${Math.round(originalSize / 1024 * 100) / 100}KB`);
|
|
77
|
+
console.log(` Optimized: ${Math.round(optimizedSize / 1024 * 100) / 100}KB`);
|
|
78
|
+
console.log(` Savings: ${Math.round(savings / 1024 * 100) / 100}KB (${savingsPercent}%)`);
|
|
79
|
+
// Update the asset with optimized CSS
|
|
80
|
+
asset.source = optimizedCSS;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Main CSS optimization function
|
|
88
|
+
*/
|
|
89
|
+
function optimizeCSS(cssContent, config) {
|
|
90
|
+
let optimizedCSS = cssContent;
|
|
91
|
+
// Step 1: Analyze usage patterns
|
|
92
|
+
const usage = analyzeUsage(cssContent, config);
|
|
93
|
+
// Step 2: Remove unused responsive breakpoint styles
|
|
94
|
+
optimizedCSS = removeUnusedBreakpoints(optimizedCSS, usage, config);
|
|
95
|
+
// Step 3: Remove unused custom properties
|
|
96
|
+
optimizedCSS = removeUnusedCustomProperties(optimizedCSS, usage, config);
|
|
97
|
+
// Step 4: Optimize CSS layers and ordering
|
|
98
|
+
optimizedCSS = optimizeCSSLayers(optimizedCSS);
|
|
99
|
+
// Step 5: Remove duplicate rules
|
|
100
|
+
optimizedCSS = removeDuplicateRules(optimizedCSS);
|
|
101
|
+
// Step 6: Minify if enabled
|
|
102
|
+
if (config.minify) {
|
|
103
|
+
optimizedCSS = minifyCSS(optimizedCSS);
|
|
104
|
+
}
|
|
105
|
+
return optimizedCSS;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Analyzes CSS usage patterns using PostCSS
|
|
109
|
+
*/
|
|
110
|
+
function analyzeUsage(cssContent, config) {
|
|
111
|
+
const usage = {
|
|
112
|
+
usedBreakpoints: new Set(),
|
|
113
|
+
usedCustomProperties: new Set(),
|
|
114
|
+
usedSelectors: new Set(),
|
|
115
|
+
unusedRules: []
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
const root = postcss.parse(cssContent);
|
|
119
|
+
// Walk through all nodes to analyze usage
|
|
120
|
+
root.walkAtRules('media', (rule) => {
|
|
121
|
+
// Analyze media query breakpoints
|
|
122
|
+
const mediaQuery = rule.params;
|
|
123
|
+
config.breakpoints.forEach((breakpoint) => {
|
|
124
|
+
const breakpointPixels = getBreakpointPixels(breakpoint);
|
|
125
|
+
// Check for pixel-based media queries
|
|
126
|
+
if (mediaQuery.includes(`${breakpointPixels}px`)) {
|
|
127
|
+
usage.usedBreakpoints.add(breakpoint);
|
|
128
|
+
}
|
|
129
|
+
// Check for CSS variable usage
|
|
130
|
+
if (mediaQuery.includes(`--cs-breakpoints-${breakpoint}`)) {
|
|
131
|
+
usage.usedBreakpoints.add(breakpoint);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
// Analyze custom property declarations and usage
|
|
136
|
+
root.walkDecls((decl) => {
|
|
137
|
+
// Find custom property definitions
|
|
138
|
+
if (decl.prop.startsWith('--')) {
|
|
139
|
+
// Check if this property is used elsewhere in the CSS
|
|
140
|
+
const propertyUsage = cssContent.includes(`var(${decl.prop})`);
|
|
141
|
+
if (propertyUsage) {
|
|
142
|
+
usage.usedCustomProperties.add(decl.prop);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Find custom property usage in values
|
|
146
|
+
const varMatches = decl.value.match(/var\(--[^)]+\)/g);
|
|
147
|
+
if (varMatches) {
|
|
148
|
+
varMatches.forEach(match => {
|
|
149
|
+
const propertyName = match.match(/--[^)]+/)?.[0];
|
|
150
|
+
if (propertyName) {
|
|
151
|
+
usage.usedCustomProperties.add(propertyName);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
// Analyze selectors
|
|
157
|
+
root.walkRules((rule) => {
|
|
158
|
+
const selectors = rule.selector.split(',');
|
|
159
|
+
selectors.forEach(selector => {
|
|
160
|
+
const trimmedSelector = selector.trim();
|
|
161
|
+
usage.usedSelectors.add(trimmedSelector);
|
|
162
|
+
// Extract class names
|
|
163
|
+
const classMatches = trimmedSelector.match(/\.[a-zA-Z0-9_-]+/g);
|
|
164
|
+
if (classMatches) {
|
|
165
|
+
classMatches.forEach(className => {
|
|
166
|
+
usage.usedSelectors.add(className);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
// For safety, always mark critical breakpoints as used
|
|
172
|
+
const criticalBreakpoints = ['xs', 'sm', 'md'];
|
|
173
|
+
criticalBreakpoints.forEach((bp) => usage.usedBreakpoints.add(bp));
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.warn('PostCSS parsing failed in usage analysis, falling back to regex:', error);
|
|
177
|
+
// Fallback to basic regex analysis
|
|
178
|
+
return analyzeUsageFallback(cssContent, config);
|
|
179
|
+
}
|
|
180
|
+
return usage;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Fallback regex-based usage analysis
|
|
184
|
+
*/
|
|
185
|
+
function analyzeUsageFallback(cssContent, config) {
|
|
186
|
+
const usage = {
|
|
187
|
+
usedBreakpoints: new Set(),
|
|
188
|
+
usedCustomProperties: new Set(),
|
|
189
|
+
usedSelectors: new Set(),
|
|
190
|
+
unusedRules: []
|
|
191
|
+
};
|
|
192
|
+
// Analyze breakpoint usage in media queries
|
|
193
|
+
config.breakpoints.forEach((breakpoint) => {
|
|
194
|
+
const varPattern = new RegExp(`var\\(--cs-breakpoints-${breakpoint}\\)`, 'g');
|
|
195
|
+
const breakpointPixels = getBreakpointPixels(breakpoint);
|
|
196
|
+
const pixelPattern = new RegExp(`(min-width|max-width):\\s*${breakpointPixels}px`, 'g');
|
|
197
|
+
const literalPattern = new RegExp(`\\b${breakpoint}\\b`, 'g');
|
|
198
|
+
if (varPattern.test(cssContent) ||
|
|
199
|
+
pixelPattern.test(cssContent) ||
|
|
200
|
+
literalPattern.test(cssContent)) {
|
|
201
|
+
usage.usedBreakpoints.add(breakpoint);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
// For safety, always mark critical breakpoints as used
|
|
205
|
+
const criticalBreakpoints = ['xs', 'sm', 'md'];
|
|
206
|
+
criticalBreakpoints.forEach((bp) => usage.usedBreakpoints.add(bp));
|
|
207
|
+
// Analyze custom property usage
|
|
208
|
+
const customPropertyMatches = cssContent.match(/var\(--[^)]+\)/g) || [];
|
|
209
|
+
customPropertyMatches.forEach(match => {
|
|
210
|
+
const propertyName = match.match(/--[^)]+/)?.[0];
|
|
211
|
+
if (propertyName) {
|
|
212
|
+
usage.usedCustomProperties.add(propertyName);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
// Analyze selector usage
|
|
216
|
+
const selectorMatches = cssContent.match(/\.[a-zA-Z0-9_-]+/g) || [];
|
|
217
|
+
selectorMatches.forEach(selector => {
|
|
218
|
+
usage.usedSelectors.add(selector);
|
|
219
|
+
});
|
|
220
|
+
return usage;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Maps breakpoint names to their pixel values
|
|
224
|
+
*/
|
|
225
|
+
function getBreakpointPixels(breakpoint) {
|
|
226
|
+
const breakpointMap = {
|
|
227
|
+
xs: 0, // mobile-first, no min-width
|
|
228
|
+
sm: 640,
|
|
229
|
+
md: 768,
|
|
230
|
+
lg: 1024,
|
|
231
|
+
xl: 1280,
|
|
232
|
+
'2xl': 1536,
|
|
233
|
+
'3xl': 1792
|
|
234
|
+
};
|
|
235
|
+
return breakpointMap[breakpoint] || 0;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Removes unused responsive breakpoint styles using PostCSS
|
|
239
|
+
*/
|
|
240
|
+
function removeUnusedBreakpoints(cssContent, usage, config) {
|
|
241
|
+
if (!config.aggressive) {
|
|
242
|
+
console.log(` Skipping breakpoint removal (non-aggressive mode)`);
|
|
243
|
+
return cssContent;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const root = postcss.parse(cssContent);
|
|
247
|
+
let removedCount = 0;
|
|
248
|
+
root.walkAtRules('media', (rule) => {
|
|
249
|
+
const mediaQuery = rule.params;
|
|
250
|
+
// Check if this media query uses an unused breakpoint
|
|
251
|
+
const usesUnusedBreakpoint = config.breakpoints.some(breakpoint => {
|
|
252
|
+
// Skip critical breakpoints
|
|
253
|
+
if (['xs', 'sm', 'md'].includes(breakpoint)) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
// Check if breakpoint is unused and referenced in this media query
|
|
257
|
+
if (!usage.usedBreakpoints.has(breakpoint)) {
|
|
258
|
+
const breakpointPixels = getBreakpointPixels(breakpoint);
|
|
259
|
+
return mediaQuery.includes(`${breakpointPixels}px`) ||
|
|
260
|
+
mediaQuery.includes(`--cs-breakpoints-${breakpoint}`);
|
|
261
|
+
}
|
|
262
|
+
return false;
|
|
263
|
+
});
|
|
264
|
+
if (usesUnusedBreakpoint) {
|
|
265
|
+
console.log(` Removed unused breakpoint media query: ${mediaQuery}`);
|
|
266
|
+
rule.remove();
|
|
267
|
+
removedCount++;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
return root.toString();
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
console.warn('PostCSS parsing failed in breakpoint removal, using original CSS:', error);
|
|
274
|
+
return cssContent;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Removes unused CSS custom properties using PostCSS
|
|
279
|
+
*/
|
|
280
|
+
function removeUnusedCustomProperties(cssContent, usage, config) {
|
|
281
|
+
try {
|
|
282
|
+
const root = postcss.parse(cssContent);
|
|
283
|
+
let removedCount = 0;
|
|
284
|
+
root.walkDecls((decl) => {
|
|
285
|
+
// Only process custom property declarations
|
|
286
|
+
if (decl.prop.startsWith('--')) {
|
|
287
|
+
// Check if property should be preserved (check this first for Tailwind variables)
|
|
288
|
+
const shouldPreserve = config.preserveCustomProperties.some(pattern => {
|
|
289
|
+
const regex = new RegExp(pattern.replace('*', '.*'));
|
|
290
|
+
return regex.test(decl.prop);
|
|
291
|
+
});
|
|
292
|
+
// Always preserve whitelisted properties (like --tw-*), otherwise check usage
|
|
293
|
+
if (!shouldPreserve && !usage.usedCustomProperties.has(decl.prop)) {
|
|
294
|
+
console.log(` Removed unused custom property: ${decl.prop}`);
|
|
295
|
+
decl.remove();
|
|
296
|
+
removedCount++;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
return root.toString();
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
console.warn('PostCSS parsing failed in custom property removal, using original CSS:', error);
|
|
304
|
+
return cssContent;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Optimizes CSS layer ordering using PostCSS
|
|
309
|
+
*/
|
|
310
|
+
function optimizeCSSLayers(cssContent) {
|
|
311
|
+
try {
|
|
312
|
+
const root = postcss.parse(cssContent);
|
|
313
|
+
const layerOrder = ['reset', 'base', 'themes', 'components', 'utilities'];
|
|
314
|
+
let hasLayers = false;
|
|
315
|
+
// Remove existing @layer declarations
|
|
316
|
+
root.walkAtRules('layer', (rule) => {
|
|
317
|
+
if (!rule.nodes || rule.nodes.length === 0) {
|
|
318
|
+
// This is a layer order declaration, not a layer block
|
|
319
|
+
hasLayers = true;
|
|
320
|
+
rule.remove();
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
// Add optimized layer order at the beginning if we found layers
|
|
324
|
+
if (hasLayers) {
|
|
325
|
+
const layerDeclaration = postcss.atRule({
|
|
326
|
+
name: 'layer',
|
|
327
|
+
params: layerOrder.join(', ')
|
|
328
|
+
});
|
|
329
|
+
root.prepend(layerDeclaration);
|
|
330
|
+
root.prepend(postcss.comment({ text: ' Optimized CSS layer order ' }));
|
|
331
|
+
}
|
|
332
|
+
return root.toString();
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
console.warn('PostCSS parsing failed in layer optimization, using original CSS:', error);
|
|
336
|
+
return cssContent;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Removes duplicate CSS rules using PostCSS
|
|
341
|
+
*/
|
|
342
|
+
function removeDuplicateRules(cssContent) {
|
|
343
|
+
try {
|
|
344
|
+
const root = postcss.parse(cssContent);
|
|
345
|
+
const ruleMap = new Map();
|
|
346
|
+
let removedCount = 0;
|
|
347
|
+
root.walkRules((rule) => {
|
|
348
|
+
const selector = rule.selector;
|
|
349
|
+
const declarations = rule.nodes?.map(node => {
|
|
350
|
+
if (node.type === 'decl') {
|
|
351
|
+
return `${node.prop}: ${node.value}`;
|
|
352
|
+
}
|
|
353
|
+
return '';
|
|
354
|
+
}).filter(Boolean).sort().join('; ');
|
|
355
|
+
const ruleKey = `${selector}|${declarations}`;
|
|
356
|
+
if (ruleMap.has(ruleKey)) {
|
|
357
|
+
console.log(` Removed duplicate rule: ${selector}`);
|
|
358
|
+
rule.remove();
|
|
359
|
+
removedCount++;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
ruleMap.set(ruleKey, selector);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
return root.toString();
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
console.warn('PostCSS parsing failed in duplicate rule removal, using original CSS:', error);
|
|
369
|
+
return cssContent;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Basic CSS minification
|
|
374
|
+
*/
|
|
375
|
+
function minifyCSS(cssContent) {
|
|
376
|
+
return cssContent
|
|
377
|
+
// Remove comments
|
|
378
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
379
|
+
// Remove unnecessary whitespace
|
|
380
|
+
.replace(/\s+/g, ' ')
|
|
381
|
+
// Remove whitespace around braces and semicolons
|
|
382
|
+
.replace(/\s*{\s*/g, '{')
|
|
383
|
+
.replace(/\s*}\s*/g, '}')
|
|
384
|
+
.replace(/\s*;\s*/g, ';')
|
|
385
|
+
.replace(/\s*:\s*/g, ':')
|
|
386
|
+
.replace(/\s*,\s*/g, ',')
|
|
387
|
+
// Remove trailing semicolons
|
|
388
|
+
.replace(/;}/g, '}')
|
|
389
|
+
// Remove leading/trailing whitespace
|
|
390
|
+
.trim();
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Generates optimization report
|
|
394
|
+
*/
|
|
395
|
+
export function generateOptimizationReport(originalCSS, optimizedCSS) {
|
|
396
|
+
const originalSize = Buffer.byteLength(originalCSS, 'utf8');
|
|
397
|
+
const optimizedSize = Buffer.byteLength(optimizedCSS, 'utf8');
|
|
398
|
+
const savings = originalSize - optimizedSize;
|
|
399
|
+
const savingsPercent = Math.round((savings / originalSize) * 100);
|
|
400
|
+
return `
|
|
401
|
+
CSS Optimization Report
|
|
402
|
+
======================
|
|
403
|
+
Original Size: ${Math.round(originalSize / 1024 * 100) / 100}KB
|
|
404
|
+
Optimized Size: ${Math.round(optimizedSize / 1024 * 100) / 100}KB
|
|
405
|
+
Savings: ${Math.round(savings / 1024 * 100) / 100}KB (${savingsPercent}%)
|
|
406
|
+
|
|
407
|
+
Optimizations Applied:
|
|
408
|
+
- Removed unused responsive breakpoint styles
|
|
409
|
+
- Purged unused CSS custom properties
|
|
410
|
+
- Optimized CSS layer ordering
|
|
411
|
+
- Removed duplicate rules
|
|
412
|
+
- Applied minification
|
|
413
|
+
`;
|
|
414
|
+
}
|