@rakeyshgidwani/roger-ui-bank-theme-harvey 0.2.52 → 0.3.1
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/layouts/adaptive-layout.d.ts +1 -0
- package/dist/components/ui/layouts/adaptive-layout.d.ts.map +1 -1
- package/dist/components/ui/layouts/adaptive-layout.esm.js +2 -2
- package/dist/components/ui/layouts/adaptive-layout.js +2 -2
- 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/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 +1027 -112
- 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/layouts/adaptive-layout.tsx +3 -1
- 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/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/components/ui/theme-toggle.css +2 -2
- 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,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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive CSS Loader Plugin
|
|
3
|
+
* Implements Phase 3 Performance Optimization: Progressive Enhancement Loading
|
|
4
|
+
*
|
|
5
|
+
* Separates CSS into:
|
|
6
|
+
* - Critical: xs-md breakpoints (mobile-tablet) - loaded immediately
|
|
7
|
+
* - Progressive: lg-3xl breakpoints (desktop+) - loaded on demand
|
|
8
|
+
*/
|
|
9
|
+
import type { Plugin } from 'vite';
|
|
10
|
+
interface ProgressiveCSSConfig {
|
|
11
|
+
critical: string[];
|
|
12
|
+
progressive: string[];
|
|
13
|
+
progressivePath: string;
|
|
14
|
+
}
|
|
15
|
+
export default function progressiveCSSLoader(config?: Partial<ProgressiveCSSConfig>): Plugin;
|
|
16
|
+
/**
|
|
17
|
+
* Creates progressive CSS loading script for runtime
|
|
18
|
+
*/
|
|
19
|
+
export declare function createProgressiveLoader(): string;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=progressive-css-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progressive-css-loader.d.ts","sourceRoot":"","sources":["../../src/plugins/progressive-css-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAGlC,UAAU,oBAAoB;IAE5B,QAAQ,EAAE,MAAM,EAAE,CAAA;IAElB,WAAW,EAAE,MAAM,EAAE,CAAA;IAErB,eAAe,EAAE,MAAM,CAAA;CACxB;AAQD,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,MAAM,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAAG,MAAM,CA6C/F;AAuID;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CA0DhD"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive CSS Loader Plugin
|
|
3
|
+
* Implements Phase 3 Performance Optimization: Progressive Enhancement Loading
|
|
4
|
+
*
|
|
5
|
+
* Separates CSS into:
|
|
6
|
+
* - Critical: xs-md breakpoints (mobile-tablet) - loaded immediately
|
|
7
|
+
* - Progressive: lg-3xl breakpoints (desktop+) - loaded on demand
|
|
8
|
+
*/
|
|
9
|
+
import postcss from 'postcss';
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
critical: ['xs', 'sm', 'md'],
|
|
12
|
+
progressive: ['lg', 'xl', '2xl', '3xl'],
|
|
13
|
+
progressivePath: 'progressive.css'
|
|
14
|
+
};
|
|
15
|
+
export default function progressiveCSSLoader(config = {}) {
|
|
16
|
+
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
17
|
+
return {
|
|
18
|
+
name: 'progressive-css-loader',
|
|
19
|
+
apply: 'build',
|
|
20
|
+
generateBundle(_options, bundle) {
|
|
21
|
+
// Find CSS assets in bundle
|
|
22
|
+
const cssAssets = Object.keys(bundle).filter(key => key.endsWith('.css'));
|
|
23
|
+
cssAssets.forEach((assetKey, index) => {
|
|
24
|
+
const asset = bundle[assetKey];
|
|
25
|
+
if (asset.type === 'asset' && typeof asset.source === 'string') {
|
|
26
|
+
const originalSource = asset.source;
|
|
27
|
+
// Safely separate CSS into critical and progressive parts
|
|
28
|
+
const { critical, progressive } = separateCSS(originalSource, finalConfig);
|
|
29
|
+
const originalSize = Math.round(originalSource.length / 1024);
|
|
30
|
+
const criticalSize = Math.round(critical.length / 1024);
|
|
31
|
+
const progressiveSize = Math.round(progressive.length / 1024);
|
|
32
|
+
console.log(` Split ${assetKey}: ${originalSize}KB -> ${criticalSize}KB critical + ${progressiveSize}KB progressive`);
|
|
33
|
+
// Update the main CSS asset to contain only critical CSS
|
|
34
|
+
asset.source = critical;
|
|
35
|
+
// Create separate progressive CSS file with unique naming for desktop enhancement
|
|
36
|
+
if (progressive.trim()) {
|
|
37
|
+
const progressiveFileName = index === 0
|
|
38
|
+
? finalConfig.progressivePath
|
|
39
|
+
: finalConfig.progressivePath.replace('.css', `-${index}.css`);
|
|
40
|
+
this.emitFile({
|
|
41
|
+
type: 'asset',
|
|
42
|
+
fileName: progressiveFileName,
|
|
43
|
+
source: progressive
|
|
44
|
+
});
|
|
45
|
+
console.log(` Created ${progressiveFileName} for desktop enhancement (${progressiveSize}KB)`);
|
|
46
|
+
}
|
|
47
|
+
// Skip creating explicit critical CSS file since main bundle already contains critical styles
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* CSS Variable to pixel value mapping based on design system
|
|
55
|
+
* This provides the authoritative mapping for breakpoint classification
|
|
56
|
+
*/
|
|
57
|
+
const BREAKPOINT_VALUES = {
|
|
58
|
+
'xs': 475,
|
|
59
|
+
'sm': 640,
|
|
60
|
+
'mobile': 640,
|
|
61
|
+
'md': 768,
|
|
62
|
+
'tablet': 768,
|
|
63
|
+
'lg': 1024,
|
|
64
|
+
'desktop': 1024,
|
|
65
|
+
'xl': 1280,
|
|
66
|
+
'wide': 1280,
|
|
67
|
+
'2xl': 1536,
|
|
68
|
+
'ultra': 1536,
|
|
69
|
+
'3xl': 1920
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Desktop breakpoint threshold - anything >= 1024px is considered desktop
|
|
73
|
+
*/
|
|
74
|
+
const DESKTOP_THRESHOLD = 1024;
|
|
75
|
+
/**
|
|
76
|
+
* Determines if a media query represents a desktop breakpoint
|
|
77
|
+
* Uses CSS variable resolution to properly classify breakpoints
|
|
78
|
+
*/
|
|
79
|
+
function isDesktopMediaQuery(mediaQuery) {
|
|
80
|
+
// Skip max-width queries - they should always stay in critical CSS
|
|
81
|
+
if (mediaQuery.includes('max-width')) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
// Check for CSS variable-based breakpoints
|
|
85
|
+
const variableMatch = mediaQuery.match(/var\(--cs-breakpoints-([^)]+)\)/);
|
|
86
|
+
if (variableMatch) {
|
|
87
|
+
const breakpointName = variableMatch[1];
|
|
88
|
+
const pixelValue = BREAKPOINT_VALUES[breakpointName];
|
|
89
|
+
if (pixelValue !== undefined) {
|
|
90
|
+
return pixelValue >= DESKTOP_THRESHOLD;
|
|
91
|
+
}
|
|
92
|
+
// If we don't recognize the variable, log it and assume it's critical for safety
|
|
93
|
+
console.warn(`Unknown breakpoint variable: --cs-breakpoints-${breakpointName}`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
// Check for direct pixel values
|
|
97
|
+
const pixelMatch = mediaQuery.match(/min-width:\s*(\d+)px/);
|
|
98
|
+
if (pixelMatch) {
|
|
99
|
+
const pixelValue = parseInt(pixelMatch[1], 10);
|
|
100
|
+
return pixelValue >= DESKTOP_THRESHOLD;
|
|
101
|
+
}
|
|
102
|
+
// If we can't classify it, keep it in critical CSS for safety
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Safely separates CSS content into critical and progressive sections
|
|
107
|
+
* Critical: < 1024px breakpoints (mobile/tablet) + base styles
|
|
108
|
+
* Progressive: >= 1024px breakpoints (desktop+) only
|
|
109
|
+
*/
|
|
110
|
+
function separateCSS(cssContent, _config) {
|
|
111
|
+
try {
|
|
112
|
+
const desktopRules = [];
|
|
113
|
+
// Parse CSS using PostCSS
|
|
114
|
+
const root = postcss.parse(cssContent);
|
|
115
|
+
// Walk through all rules and find desktop media queries
|
|
116
|
+
root.walkAtRules('media', (rule) => {
|
|
117
|
+
const mediaQuery = rule.params;
|
|
118
|
+
const shouldExtract = isDesktopMediaQuery(mediaQuery);
|
|
119
|
+
if (shouldExtract) {
|
|
120
|
+
// Convert the rule back to CSS string and collect it
|
|
121
|
+
desktopRules.push(rule.toString());
|
|
122
|
+
// Remove the rule from the original AST
|
|
123
|
+
rule.remove();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// Generate the critical CSS (without desktop rules)
|
|
127
|
+
const criticalCSS = root.toString();
|
|
128
|
+
const progressiveCSS = desktopRules.join('\n');
|
|
129
|
+
// Validate CSS syntax by checking for balanced braces
|
|
130
|
+
const criticalOpenBraces = (criticalCSS.match(/\{/g) || []).length;
|
|
131
|
+
const criticalCloseBraces = (criticalCSS.match(/\}/g) || []).length;
|
|
132
|
+
const progressiveOpenBraces = (progressiveCSS.match(/\{/g) || []).length;
|
|
133
|
+
const progressiveCloseBraces = (progressiveCSS.match(/\}/g) || []).length;
|
|
134
|
+
if (criticalOpenBraces !== criticalCloseBraces) {
|
|
135
|
+
console.error(`PostCSS extraction created malformed critical CSS: ${criticalOpenBraces} open braces vs ${criticalCloseBraces} close braces`);
|
|
136
|
+
console.log('Falling back to original CSS to avoid syntax errors');
|
|
137
|
+
return {
|
|
138
|
+
critical: cssContent,
|
|
139
|
+
progressive: ''
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (progressiveOpenBraces !== progressiveCloseBraces) {
|
|
143
|
+
console.error(`PostCSS extraction created malformed progressive CSS: ${progressiveOpenBraces} open braces vs ${progressiveCloseBraces} close braces`);
|
|
144
|
+
console.log('Falling back to original CSS to avoid syntax errors');
|
|
145
|
+
return {
|
|
146
|
+
critical: cssContent,
|
|
147
|
+
progressive: ''
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
console.log(`CSS separation: Extracted ${desktopRules.length} desktop-only media queries using PostCSS`);
|
|
151
|
+
// Note: Any remaining CSS syntax warnings are from pre-existing CSS generation issues,
|
|
152
|
+
// not from the progressive CSS extraction process
|
|
153
|
+
return {
|
|
154
|
+
critical: criticalCSS,
|
|
155
|
+
progressive: progressiveCSS
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.error('PostCSS separation failed, falling back to original CSS:', error);
|
|
160
|
+
return {
|
|
161
|
+
critical: cssContent,
|
|
162
|
+
progressive: ''
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Creates progressive CSS loading script for runtime
|
|
168
|
+
*/
|
|
169
|
+
export function createProgressiveLoader() {
|
|
170
|
+
return `
|
|
171
|
+
// Progressive CSS Loader - Phase 3 Performance Optimization
|
|
172
|
+
(function() {
|
|
173
|
+
'use strict';
|
|
174
|
+
|
|
175
|
+
const DESKTOP_BREAKPOINT = 1024; // lg breakpoint
|
|
176
|
+
const PROGRESSIVE_CSS_PATH = '/assets/progressive.css';
|
|
177
|
+
|
|
178
|
+
let progressiveCSSLoaded = false;
|
|
179
|
+
|
|
180
|
+
function loadProgressiveCSS() {
|
|
181
|
+
if (progressiveCSSLoaded) return;
|
|
182
|
+
|
|
183
|
+
const link = document.createElement('link');
|
|
184
|
+
link.rel = 'stylesheet';
|
|
185
|
+
link.href = PROGRESSIVE_CSS_PATH;
|
|
186
|
+
link.media = 'screen and (min-width: ' + DESKTOP_BREAKPOINT + 'px)';
|
|
187
|
+
|
|
188
|
+
// Load asynchronously for non-blocking
|
|
189
|
+
link.onload = function() {
|
|
190
|
+
console.log('Progressive CSS loaded for desktop');
|
|
191
|
+
progressiveCSSLoaded = true;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
document.head.appendChild(link);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function checkViewport() {
|
|
198
|
+
if (window.innerWidth >= DESKTOP_BREAKPOINT) {
|
|
199
|
+
loadProgressiveCSS();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Load on resize to desktop
|
|
204
|
+
let resizeTimeout;
|
|
205
|
+
window.addEventListener('resize', function() {
|
|
206
|
+
clearTimeout(resizeTimeout);
|
|
207
|
+
resizeTimeout = setTimeout(checkViewport, 150);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Check initial viewport
|
|
211
|
+
if (document.readyState === 'loading') {
|
|
212
|
+
document.addEventListener('DOMContentLoaded', checkViewport);
|
|
213
|
+
} else {
|
|
214
|
+
checkViewport();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Preload for fast desktop switches
|
|
218
|
+
if (window.innerWidth >= 768) { // tablet+
|
|
219
|
+
const preload = document.createElement('link');
|
|
220
|
+
preload.rel = 'preload';
|
|
221
|
+
preload.href = PROGRESSIVE_CSS_PATH;
|
|
222
|
+
preload.as = 'style';
|
|
223
|
+
document.head.appendChild(preload);
|
|
224
|
+
}
|
|
225
|
+
})();
|
|
226
|
+
`;
|
|
227
|
+
}
|