@rakeyshgidwani/roger-ui-bank-theme-harvey 0.2.52 → 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.
Files changed (125) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/components/ui/button.d.ts +3 -1
  3. package/dist/components/ui/button.d.ts.map +1 -1
  4. package/dist/components/ui/button.esm.js +3 -2
  5. package/dist/components/ui/button.js +3 -2
  6. package/dist/components/ui/layout/container.d.ts +57 -0
  7. package/dist/components/ui/layout/container.d.ts.map +1 -0
  8. package/dist/components/ui/layout/container.esm.js +173 -0
  9. package/dist/components/ui/layout/container.js +173 -0
  10. package/dist/components/ui/layout/index.d.ts +9 -0
  11. package/dist/components/ui/layout/index.d.ts.map +1 -0
  12. package/dist/components/ui/layout/index.esm.js +6 -0
  13. package/dist/components/ui/layout/index.js +6 -0
  14. package/dist/components/ui/layout/responsive-grid.d.ts +93 -0
  15. package/dist/components/ui/layout/responsive-grid.d.ts.map +1 -0
  16. package/dist/components/ui/layout/responsive-grid.esm.js +124 -0
  17. package/dist/components/ui/layout/responsive-grid.js +124 -0
  18. package/dist/components/ui/navigation/index.d.ts +2 -1
  19. package/dist/components/ui/navigation/index.d.ts.map +1 -1
  20. package/dist/components/ui/navigation/index.esm.js +1 -0
  21. package/dist/components/ui/navigation/index.js +1 -0
  22. package/dist/components/ui/navigation/progressive-navigation.d.ts +37 -0
  23. package/dist/components/ui/navigation/progressive-navigation.d.ts.map +1 -0
  24. package/dist/components/ui/navigation/progressive-navigation.esm.js +145 -0
  25. package/dist/components/ui/navigation/progressive-navigation.js +145 -0
  26. package/dist/components/ui/navigation/types.d.ts +21 -0
  27. package/dist/components/ui/navigation/types.d.ts.map +1 -1
  28. package/dist/hooks/use-adaptive-layout.d.ts +2 -1
  29. package/dist/hooks/use-adaptive-layout.d.ts.map +1 -1
  30. package/dist/hooks/use-adaptive-layout.esm.js +13 -8
  31. package/dist/hooks/use-adaptive-layout.js +13 -8
  32. package/dist/hooks/use-device.d.ts +3 -1
  33. package/dist/hooks/use-device.d.ts.map +1 -1
  34. package/dist/hooks/use-device.esm.js +14 -7
  35. package/dist/hooks/use-device.js +14 -7
  36. package/dist/index.d.ts +19 -4
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.esm.js +9 -4
  39. package/dist/index.js +9 -4
  40. package/dist/plugins/css-purge-optimizer.d.ts +25 -0
  41. package/dist/plugins/css-purge-optimizer.d.ts.map +1 -0
  42. package/dist/plugins/css-purge-optimizer.esm.js +414 -0
  43. package/dist/plugins/css-purge-optimizer.js +414 -0
  44. package/dist/plugins/performance-monitor.d.ts +29 -0
  45. package/dist/plugins/performance-monitor.d.ts.map +1 -0
  46. package/dist/plugins/performance-monitor.esm.js +221 -0
  47. package/dist/plugins/performance-monitor.js +221 -0
  48. package/dist/plugins/progressive-css-loader.d.ts +21 -0
  49. package/dist/plugins/progressive-css-loader.d.ts.map +1 -0
  50. package/dist/plugins/progressive-css-loader.esm.js +227 -0
  51. package/dist/plugins/progressive-css-loader.js +227 -0
  52. package/dist/plugins/theme-css-generator.d.ts.map +1 -1
  53. package/dist/plugins/theme-css-generator.esm.js +19 -6
  54. package/dist/plugins/theme-css-generator.js +19 -6
  55. package/dist/styles.css +1025 -110
  56. package/dist/theme.d.ts.map +1 -1
  57. package/dist/theme.esm.js +4 -1
  58. package/dist/theme.js +4 -1
  59. package/dist/themes/phase1-constants.d.ts +23 -0
  60. package/dist/themes/phase1-constants.d.ts.map +1 -0
  61. package/dist/themes/phase1-constants.esm.js +180 -0
  62. package/dist/themes/phase1-constants.js +180 -0
  63. package/dist/themes/themes/default.d.ts.map +1 -1
  64. package/dist/themes/themes/default.esm.js +4 -1
  65. package/dist/themes/themes/default.js +4 -1
  66. package/dist/themes/themes/harvey.d.ts.map +1 -1
  67. package/dist/themes/themes/harvey.esm.js +4 -1
  68. package/dist/themes/themes/harvey.js +4 -1
  69. package/dist/themes/types.d.ts +62 -0
  70. package/dist/themes/types.d.ts.map +1 -1
  71. package/dist/themes/validation.d.ts +17 -0
  72. package/dist/themes/validation.d.ts.map +1 -1
  73. package/dist/themes/validation.esm.js +218 -0
  74. package/dist/themes/validation.js +218 -0
  75. package/dist/types.d.ts +62 -0
  76. package/dist/types.d.ts.map +1 -1
  77. package/dist/utils/progressive-css-injector.d.ts +80 -0
  78. package/dist/utils/progressive-css-injector.d.ts.map +1 -0
  79. package/dist/utils/progressive-css-injector.esm.js +217 -0
  80. package/dist/utils/progressive-css-injector.js +217 -0
  81. package/package.json +1 -1
  82. package/src/components/ui/button.tsx +9 -6
  83. package/src/components/ui/layout/container.tsx +312 -0
  84. package/src/components/ui/layout/index.ts +10 -0
  85. package/src/components/ui/layout/responsive-grid.tsx +286 -0
  86. package/src/components/ui/navigation/index.ts +2 -0
  87. package/src/components/ui/navigation/progressive-navigation.tsx +453 -0
  88. package/src/components/ui/navigation/types.ts +41 -0
  89. package/src/hooks/use-adaptive-layout.ts +13 -9
  90. package/src/hooks/use-device.tsx +17 -10
  91. package/src/index.ts +19 -4
  92. package/src/plugins/css-purge-optimizer.ts +491 -0
  93. package/src/plugins/performance-monitor.ts +292 -0
  94. package/src/plugins/progressive-css-loader.ts +269 -0
  95. package/src/plugins/theme-css-generator.ts +22 -6
  96. package/src/styles/components/base/badge.css +2 -2
  97. package/src/styles/components/base/button.css +238 -35
  98. package/src/styles/components/base/card.css +2 -2
  99. package/src/styles/components/base/checkbox.css +3 -3
  100. package/src/styles/components/base/label.css +3 -3
  101. package/src/styles/components/feedback/skeleton.css +1 -1
  102. package/src/styles/components/feedback/toast.css +1 -1
  103. package/src/styles/components/index.css +3 -0
  104. package/src/styles/components/layout/container.css +466 -0
  105. package/src/styles/components/layout/index.css +5 -0
  106. package/src/styles/components/layout/responsive-grid.css +422 -0
  107. package/src/styles/components/navigation/breadcrumb.css +1 -1
  108. package/src/styles/components/navigation/index.css +1 -0
  109. package/src/styles/components/navigation/menu.css +2 -2
  110. package/src/styles/components/navigation/pagination.css +4 -4
  111. package/src/styles/components/navigation/progressive-navigation.css +633 -0
  112. package/src/styles/components/navigation/sidebar.css +4 -4
  113. package/src/styles/components/navigation/stepper.css +2 -2
  114. package/src/styles/components/navigation/tabs.css +1 -1
  115. package/src/styles/progressive.css +17 -0
  116. package/src/styles/themes/harvey.css +103 -19
  117. package/src/styles/utilities/semantic-input-system.css +7 -13
  118. package/src/theme.ts +5 -1
  119. package/src/themes/phase1-constants.ts +189 -0
  120. package/src/themes/themes/default.ts +5 -1
  121. package/src/themes/themes/harvey.ts +5 -1
  122. package/src/themes/types.ts +77 -1
  123. package/src/themes/validation.ts +249 -0
  124. package/src/types.ts +77 -1
  125. 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
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Performance Monitor Plugin
3
+ * Implements Phase 3 Performance Optimization: Bundle Size Monitoring
4
+ *
5
+ * Tracks:
6
+ * - CSS bundle sizes (critical vs progressive)
7
+ * - JavaScript bundle sizes
8
+ * - Performance metrics and alerts
9
+ * - Loading performance analysis
10
+ */
11
+ import type { Plugin } from 'vite';
12
+ interface PerformanceConfig {
13
+ thresholds: {
14
+ criticalCSS: number;
15
+ progressiveCSS: number;
16
+ totalCSS: number;
17
+ javascript: number;
18
+ };
19
+ reportPath: string;
20
+ enableAlerts: boolean;
21
+ baselinePath?: string;
22
+ }
23
+ export default function performanceMonitor(config?: Partial<PerformanceConfig>): Plugin;
24
+ /**
25
+ * Creates performance comparison utility
26
+ */
27
+ export declare function comparePerformance(currentReport: string, baselineReport?: string): void;
28
+ export {};
29
+ //# sourceMappingURL=performance-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"performance-monitor.d.ts","sourceRoot":"","sources":["../../src/plugins/performance-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAIlC,UAAU,iBAAiB;IAEzB,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAA;QACnB,cAAc,EAAE,MAAM,CAAA;QACtB,QAAQ,EAAE,MAAM,CAAA;QAChB,UAAU,EAAE,MAAM,CAAA;KACnB,CAAA;IAED,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAsCD,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,MAAM,GAAE,OAAO,CAAC,iBAAiB,CAAM,GAAG,MAAM,CAwB1F;AAwKD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CA+BvF"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Performance Monitor Plugin
3
+ * Implements Phase 3 Performance Optimization: Bundle Size Monitoring
4
+ *
5
+ * Tracks:
6
+ * - CSS bundle sizes (critical vs progressive)
7
+ * - JavaScript bundle sizes
8
+ * - Performance metrics and alerts
9
+ * - Loading performance analysis
10
+ */
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ const DEFAULT_CONFIG = {
14
+ thresholds: {
15
+ criticalCSS: 50, // KB - Mobile-critical styles should be minimal
16
+ progressiveCSS: 150, // KB - Desktop enhancement styles
17
+ totalCSS: 200, // KB - Total CSS budget
18
+ javascript: 500 // KB - JS bundle threshold
19
+ },
20
+ reportPath: 'performance-report.json',
21
+ enableAlerts: true
22
+ };
23
+ export default function performanceMonitor(config = {}) {
24
+ const finalConfig = { ...DEFAULT_CONFIG, ...config };
25
+ const startTime = Date.now();
26
+ return {
27
+ name: 'performance-monitor',
28
+ apply: 'build',
29
+ generateBundle(_options, bundle) {
30
+ const analysis = analyzeBundle(bundle, startTime, finalConfig);
31
+ // Write performance report
32
+ const reportPath = path.resolve(finalConfig.reportPath);
33
+ fs.writeFileSync(reportPath, JSON.stringify(analysis, null, 2));
34
+ // Console output
35
+ logPerformanceReport(analysis, finalConfig);
36
+ // Alerts
37
+ if (finalConfig.enableAlerts && analysis.alerts.length > 0) {
38
+ console.warn('\nāš ļø Performance Alerts:');
39
+ analysis.alerts.forEach(alert => console.warn(` ${alert}`));
40
+ }
41
+ }
42
+ };
43
+ }
44
+ /**
45
+ * Analyzes bundle contents and generates performance report
46
+ */
47
+ function analyzeBundle(bundle, startTime, config) {
48
+ const analysis = {
49
+ timestamp: new Date().toISOString(),
50
+ files: {},
51
+ totals: {
52
+ criticalCSS: 0,
53
+ progressiveCSS: 0,
54
+ totalCSS: 0,
55
+ javascript: 0,
56
+ assets: 0,
57
+ total: 0
58
+ },
59
+ performance: {
60
+ bundleTime: Date.now() - startTime,
61
+ compressionRatio: 0
62
+ },
63
+ alerts: []
64
+ };
65
+ // Analyze each file in the bundle
66
+ Object.entries(bundle).forEach(([fileName, asset]) => {
67
+ if (asset.type === 'asset' || asset.type === 'chunk') {
68
+ const size = getAssetSize(asset);
69
+ const type = getAssetType(fileName);
70
+ const category = getAssetCategory(fileName);
71
+ analysis.files[fileName] = {
72
+ size,
73
+ type,
74
+ category
75
+ };
76
+ // Update totals
77
+ if (type === 'css') {
78
+ if (category === 'critical') {
79
+ analysis.totals.criticalCSS += size;
80
+ }
81
+ else if (category === 'progressive') {
82
+ analysis.totals.progressiveCSS += size;
83
+ }
84
+ analysis.totals.totalCSS += size;
85
+ }
86
+ else if (type === 'js') {
87
+ analysis.totals.javascript += size;
88
+ }
89
+ else {
90
+ analysis.totals.assets += size;
91
+ }
92
+ analysis.totals.total += size;
93
+ }
94
+ });
95
+ // Calculate content-aware estimated gzip compression ratio
96
+ const calculateEstimatedGzipRatio = () => {
97
+ // Realistic gzip compression ratios by content type (compressed size / original size)
98
+ const cssGzipRatio = 0.15; // CSS compresses to ~15% of original size
99
+ const jsGzipRatio = 0.25; // JavaScript compresses to ~25% of original size
100
+ const cssSize = analysis.totals.totalCSS;
101
+ const jsSize = analysis.totals.javascript;
102
+ // Only calculate gzip for CSS and JS (assets like fonts/images typically aren't gzipped by CDNs)
103
+ const compressibleSize = cssSize + jsSize;
104
+ if (compressibleSize === 0)
105
+ return 0;
106
+ // Calculate estimated gzipped size for compressible content only
107
+ const estimatedGzipSize = (cssSize * cssGzipRatio) +
108
+ (jsSize * jsGzipRatio);
109
+ // Return ratio of estimated gzipped size to total compressible size
110
+ return Math.round((estimatedGzipSize / compressibleSize) * 100) / 100;
111
+ };
112
+ analysis.performance.compressionRatio = calculateEstimatedGzipRatio();
113
+ // Generate alerts
114
+ analysis.alerts = generateAlerts(analysis.totals, config.thresholds);
115
+ return analysis;
116
+ }
117
+ /**
118
+ * Determines asset size in KB
119
+ */
120
+ function getAssetSize(asset) {
121
+ const source = asset.source || asset.code || '';
122
+ const bytes = typeof source === 'string' ? Buffer.byteLength(source, 'utf8') : source.length;
123
+ return Math.round(bytes / 1024 * 100) / 100; // KB with 2 decimal precision
124
+ }
125
+ /**
126
+ * Determines asset type from filename
127
+ */
128
+ function getAssetType(fileName) {
129
+ if (fileName.endsWith('.css'))
130
+ return 'css';
131
+ if (fileName.endsWith('.js') || fileName.endsWith('.mjs'))
132
+ return 'js';
133
+ return 'asset';
134
+ }
135
+ /**
136
+ * Categorizes assets for performance analysis
137
+ */
138
+ function getAssetCategory(fileName) {
139
+ if (fileName.includes('critical'))
140
+ return 'critical';
141
+ if (fileName.includes('progressive'))
142
+ return 'progressive';
143
+ if (fileName.includes('vendor') || fileName.includes('node_modules'))
144
+ return 'vendor';
145
+ if (fileName.includes('component'))
146
+ return 'component';
147
+ return undefined;
148
+ }
149
+ /**
150
+ * Generates performance alerts based on thresholds
151
+ */
152
+ function generateAlerts(totals, thresholds) {
153
+ const alerts = [];
154
+ if (totals.criticalCSS > thresholds.criticalCSS) {
155
+ alerts.push(`Critical CSS size (${totals.criticalCSS}KB) exceeds threshold (${thresholds.criticalCSS}KB)`);
156
+ }
157
+ if (totals.progressiveCSS > thresholds.progressiveCSS) {
158
+ alerts.push(`Progressive CSS size (${totals.progressiveCSS}KB) exceeds threshold (${thresholds.progressiveCSS}KB)`);
159
+ }
160
+ if (totals.totalCSS > thresholds.totalCSS) {
161
+ alerts.push(`Total CSS size (${totals.totalCSS}KB) exceeds threshold (${thresholds.totalCSS}KB)`);
162
+ }
163
+ if (totals.javascript > thresholds.javascript) {
164
+ alerts.push(`JavaScript bundle size (${totals.javascript}KB) exceeds threshold (${thresholds.javascript}KB)`);
165
+ }
166
+ return alerts;
167
+ }
168
+ /**
169
+ * Logs performance report to console
170
+ */
171
+ function logPerformanceReport(analysis, config) {
172
+ console.log('\nšŸ“Š Performance Analysis Report');
173
+ console.log('================================');
174
+ console.log('\nšŸ“¦ Bundle Sizes:');
175
+ console.log(` Critical CSS: ${analysis.totals.criticalCSS}KB (threshold: ${config.thresholds.criticalCSS}KB)`);
176
+ console.log(` Progressive CSS: ${analysis.totals.progressiveCSS}KB (threshold: ${config.thresholds.progressiveCSS}KB)`);
177
+ console.log(` Total CSS: ${analysis.totals.totalCSS}KB (threshold: ${config.thresholds.totalCSS}KB)`);
178
+ console.log(` JavaScript: ${analysis.totals.javascript}KB (threshold: ${config.thresholds.javascript}KB)`);
179
+ console.log(` Assets: ${analysis.totals.assets}KB`);
180
+ console.log(` Total Bundle: ${analysis.totals.total}KB`);
181
+ console.log('\n⚔ Performance Metrics:');
182
+ console.log(` Build Time: ${analysis.performance.bundleTime}ms`);
183
+ const compressibleSize = analysis.totals.totalCSS + analysis.totals.javascript;
184
+ console.log(` Gzip Ratio: ${analysis.performance.compressionRatio} (CSS+JS compression)`);
185
+ console.log(` Est. Gzip Size: ~${Math.round(compressibleSize * analysis.performance.compressionRatio)}KB (CSS+JS only)`);
186
+ console.log(`\nšŸ“ Report saved to: ${config.reportPath}`);
187
+ if (analysis.alerts.length === 0) {
188
+ console.log('\nāœ… All performance thresholds met!');
189
+ }
190
+ }
191
+ /**
192
+ * Creates performance comparison utility
193
+ */
194
+ export function comparePerformance(currentReport, baselineReport) {
195
+ if (!baselineReport || !fs.existsSync(baselineReport)) {
196
+ console.log('\nšŸ“ˆ Baseline report not found. Current report will serve as baseline.');
197
+ return;
198
+ }
199
+ try {
200
+ const current = JSON.parse(fs.readFileSync(currentReport, 'utf8'));
201
+ const baseline = JSON.parse(fs.readFileSync(baselineReport, 'utf8'));
202
+ console.log('\nšŸ“ˆ Performance Comparison (vs baseline)');
203
+ console.log('======================================');
204
+ const metrics = [
205
+ { name: 'Critical CSS', current: current.totals.criticalCSS, baseline: baseline.totals.criticalCSS },
206
+ { name: 'Progressive CSS', current: current.totals.progressiveCSS, baseline: baseline.totals.progressiveCSS },
207
+ { name: 'Total CSS', current: current.totals.totalCSS, baseline: baseline.totals.totalCSS },
208
+ { name: 'JavaScript', current: current.totals.javascript, baseline: baseline.totals.javascript },
209
+ { name: 'Total Bundle', current: current.totals.total, baseline: baseline.totals.total }
210
+ ];
211
+ metrics.forEach(metric => {
212
+ const diff = metric.current - metric.baseline;
213
+ const diffPercent = metric.baseline > 0 ? Math.round((diff / metric.baseline) * 100) : 0;
214
+ const indicator = diff > 0 ? 'šŸ“ˆ' : diff < 0 ? 'šŸ“‰' : 'āž”ļø';
215
+ console.log(` ${metric.name.padEnd(15)} ${indicator} ${metric.current}KB (${diff >= 0 ? '+' : ''}${diff}KB, ${diffPercent >= 0 ? '+' : ''}${diffPercent}%)`);
216
+ });
217
+ }
218
+ catch (error) {
219
+ console.error('Failed to compare performance reports:', error);
220
+ }
221
+ }