bertui 0.4.5 → 0.4.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bertui",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Lightning-fast React dev server powered by Bun and Elysia",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -1,238 +1,22 @@
1
- // bertui/src/build/image-optimizer.js - FIXED VERSION
1
+ // bertui/src/build/image-optimizer.js - SIMPLE WORKING VERSION
2
2
  import { join, extname } from 'path';
3
3
  import { existsSync, mkdirSync, readdirSync, cpSync } from 'fs';
4
4
  import logger from '../logger/logger.js';
5
5
 
6
6
  /**
7
- * 🎯 WASM-powered image optimization using @jsquash
8
- * Zero OS dependencies, pure JavaScript, blazing fast!
9
- */
10
-
11
- // Lazy-load WASM modules (only when needed)
12
- let pngEncode, pngDecode;
13
- let jpegEncode, jpegDecode;
14
- let webpEncode, webpDecode;
15
-
16
- async function initializePNG() {
17
- if (!pngEncode) {
18
- const { encode, decode } = await import('@jsquash/png');
19
- pngEncode = encode;
20
- pngDecode = decode;
21
- }
22
- }
23
-
24
- async function initializeJPEG() {
25
- if (!jpegEncode) {
26
- const { encode, decode } = await import('@jsquash/jpeg');
27
- jpegEncode = encode;
28
- jpegDecode = decode;
29
- }
30
- }
31
-
32
- async function initializeWebP() {
33
- if (!webpEncode) {
34
- const { encode, decode } = await import('@jsquash/webp');
35
- webpEncode = encode;
36
- webpDecode = decode;
37
- }
38
- }
39
-
40
- /**
41
- * Optimize images using WASM-powered codecs
42
- * This is FAST and has ZERO OS dependencies! 🚀
7
+ * Simple image copying - skip WASM optimization for now
43
8
  */
44
9
  export async function optimizeImages(srcDir, outDir) {
45
- const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg'];
46
- let optimized = 0;
47
- let totalSaved = 0;
48
-
49
- logger.info(`🖼️ Optimizing images from ${srcDir} to ${outDir}...`);
50
-
51
- // Check if source directory exists
52
- if (!existsSync(srcDir)) {
53
- logger.warn(`⚠️ Source directory not found: ${srcDir}`);
54
- return { optimized: 0, saved: 0 };
55
- }
56
-
57
- // Create output directory if it doesn't exist
58
- if (!existsSync(outDir)) {
59
- mkdirSync(outDir, { recursive: true });
60
- }
61
-
62
- async function processDirectory(dir, targetDir) {
63
- const entries = readdirSync(dir, { withFileTypes: true });
64
-
65
- for (const entry of entries) {
66
- const srcPath = join(dir, entry.name);
67
- const destPath = join(targetDir, entry.name);
68
-
69
- if (entry.isDirectory()) {
70
- // Create subdirectory in target
71
- const subDestPath = join(targetDir, entry.name);
72
- if (!existsSync(subDestPath)) {
73
- mkdirSync(subDestPath, { recursive: true });
74
- }
75
- await processDirectory(srcPath, subDestPath);
76
- } else if (entry.isFile()) {
77
- const ext = extname(entry.name).toLowerCase();
78
-
79
- if (imageExtensions.includes(ext)) {
80
- try {
81
- const result = await optimizeImage(srcPath, destPath);
82
- if (result) {
83
- optimized++;
84
- totalSaved += result.saved;
85
- const savedPercent = ((result.saved / result.originalSize) * 100).toFixed(1);
86
- logger.debug(
87
- `✨ ${entry.name}: ${(result.originalSize / 1024).toFixed(1)}KB → ${(result.newSize / 1024).toFixed(1)}KB (-${savedPercent}%)`
88
- );
89
- }
90
- } catch (error) {
91
- logger.warn(`⚠️ Failed to optimize ${entry.name}: ${error.message}`);
92
- // Fallback: just copy the file
93
- try {
94
- cpSync(srcPath, destPath);
95
- logger.debug(`📋 Copied ${entry.name} (fallback)`);
96
- } catch (copyError) {
97
- logger.error(`❌ Failed to copy ${entry.name}: ${copyError.message}`);
98
- }
99
- }
100
- }
101
- }
102
- }
103
- }
104
-
105
- await processDirectory(srcDir, outDir);
106
-
107
- if (optimized > 0) {
108
- logger.success(
109
- `✅ Optimized ${optimized} images (saved ${(totalSaved / 1024).toFixed(2)}KB total)`
110
- );
111
- } else {
112
- logger.info(`📋 No images optimized (copied ${countFilesInDir(outDir)} files)`);
113
- }
114
-
115
- return { optimized, saved: totalSaved };
10
+ logger.info(`📋 Copying images from ${srcDir} to ${outDir}...`);
11
+ const copied = copyImages(srcDir, outDir);
12
+ return { optimized: 0, saved: 0, copied };
116
13
  }
117
14
 
118
- /**
119
- * Count files in directory (for logging)
120
- */
121
- function countFilesInDir(dir) {
122
- if (!existsSync(dir)) return 0;
123
-
124
- let count = 0;
125
- const entries = readdirSync(dir, { withFileTypes: true });
126
-
127
- for (const entry of entries) {
128
- if (entry.isFile()) {
129
- count++;
130
- } else if (entry.isDirectory()) {
131
- count += countFilesInDir(join(dir, entry.name));
132
- }
133
- }
134
-
135
- return count;
136
- }
137
-
138
- /**
139
- * Optimize a single image using WASM codecs
140
- */
141
- async function optimizeImage(srcPath, destPath) {
142
- const ext = extname(srcPath).toLowerCase();
143
- const originalFile = Bun.file(srcPath);
144
-
145
- // Check if file exists
146
- if (!await originalFile.exists()) {
147
- throw new Error(`File not found: ${srcPath}`);
148
- }
149
-
150
- const originalSize = originalFile.size;
151
-
152
- try {
153
- // For SVG and GIF, just copy (no optimization needed/supported)
154
- if (ext === '.svg' || ext === '.gif') {
155
- cpSync(srcPath, destPath);
156
- return null;
157
- }
158
-
159
- // Read the original image
160
- const originalBuffer = await originalFile.arrayBuffer();
161
-
162
- let optimizedBuffer;
163
-
164
- if (ext === '.png') {
165
- await initializePNG();
166
-
167
- // Decode → Re-encode with compression
168
- const imageData = await pngDecode(originalBuffer);
169
-
170
- // Encode with oxipng-level compression (quality 85, similar to oxipng -o 2)
171
- optimizedBuffer = await pngEncode(imageData, {
172
- quality: 85,
173
- effort: 2 // 0-10, higher = better compression but slower
174
- });
175
-
176
- } else if (ext === '.jpg' || ext === '.jpeg') {
177
- await initializeJPEG();
178
-
179
- // Decode → Re-encode with quality 85 (mozjpeg-like quality)
180
- const imageData = await jpegDecode(originalBuffer);
181
- optimizedBuffer = await jpegEncode(imageData, { quality: 85 });
182
-
183
- } else if (ext === '.webp') {
184
- await initializeWebP();
185
-
186
- // WebP optimization
187
- const imageData = await webpDecode(originalBuffer);
188
- optimizedBuffer = await webpEncode(imageData, { quality: 85 });
189
- } else {
190
- // Unsupported format, just copy
191
- cpSync(srcPath, destPath);
192
- return null;
193
- }
194
-
195
- // Only save if we actually reduced the size
196
- if (optimizedBuffer && optimizedBuffer.byteLength < originalSize) {
197
- await Bun.write(destPath, optimizedBuffer);
198
- const saved = originalSize - optimizedBuffer.byteLength;
199
- return { saved, originalSize, newSize: optimizedBuffer.byteLength };
200
- }
201
-
202
- // If optimization didn't help, just copy the original
203
- cpSync(srcPath, destPath);
204
- return null;
205
-
206
- } catch (error) {
207
- // If anything fails, just copy the original
208
- logger.warn(`Optimization failed for ${srcPath.split('/').pop()}, copying original`);
209
- cpSync(srcPath, destPath);
210
- return null;
211
- }
212
- }
213
-
214
- /**
215
- * Check if optimization is available (always true with WASM! 🎉)
216
- */
217
15
  export async function checkOptimizationTools() {
218
- try {
219
- // Try to import the WASM modules
220
- await import('@jsquash/png');
221
- await import('@jsquash/jpeg');
222
- await import('@jsquash/webp');
223
-
224
- logger.success('✅ WASM image optimization available');
225
- logger.info('📦 Using @jsquash (zero OS dependencies!)');
226
- return ['png', 'jpeg', 'webp'];
227
- } catch (error) {
228
- logger.error('❌ WASM codecs not installed. Run: bun add @jsquash/png @jsquash/jpeg @jsquash/webp');
229
- return [];
230
- }
16
+ logger.info('📋 Image optimization disabled (simple mode)');
17
+ return [];
231
18
  }
232
19
 
233
- /**
234
- * Copy images without optimization (fallback)
235
- */
236
20
  export function copyImages(srcDir, outDir) {
237
21
  const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.avif', '.ico'];
238
22
  let copied = 0;
@@ -246,43 +30,55 @@ export function copyImages(srcDir, outDir) {
246
30
  // Create output directory if it doesn't exist
247
31
  if (!existsSync(outDir)) {
248
32
  mkdirSync(outDir, { recursive: true });
33
+ logger.info(`Created directory: ${outDir}`);
249
34
  }
250
35
 
251
36
  function processDirectory(dir, targetDir) {
252
- const entries = readdirSync(dir, { withFileTypes: true });
37
+ try {
38
+ const entries = readdirSync(dir, { withFileTypes: true });
39
+
40
+ if (entries.length === 0) {
41
+ logger.warn(`Directory empty: ${dir}`);
42
+ return;
43
+ }
253
44
 
254
- for (const entry of entries) {
255
- const srcPath = join(dir, entry.name);
256
- const destPath = join(targetDir, entry.name);
45
+ for (const entry of entries) {
46
+ const srcPath = join(dir, entry.name);
47
+ const destPath = join(targetDir, entry.name);
257
48
 
258
- if (entry.isDirectory()) {
259
- // Create subdirectory in target
260
- const subDestPath = join(targetDir, entry.name);
261
- if (!existsSync(subDestPath)) {
262
- mkdirSync(subDestPath, { recursive: true });
263
- }
264
- // FIXED: Use destPath, not targetDir
265
- processDirectory(srcPath, subDestPath);
266
- } else if (entry.isFile()) {
267
- const ext = extname(entry.name).toLowerCase();
49
+ if (entry.isDirectory()) {
50
+ // Create subdirectory in target
51
+ const subDestPath = join(targetDir, entry.name);
52
+ if (!existsSync(subDestPath)) {
53
+ mkdirSync(subDestPath, { recursive: true });
54
+ }
55
+ processDirectory(srcPath, subDestPath);
56
+ } else if (entry.isFile()) {
57
+ const ext = extname(entry.name).toLowerCase();
268
58
 
269
- if (imageExtensions.includes(ext)) {
270
- try {
271
- cpSync(srcPath, destPath);
272
- copied++;
273
- logger.debug(`📋 Copied ${entry.name}`);
274
- } catch (error) {
275
- logger.warn(`Failed to copy ${entry.name}: ${error.message}`);
59
+ if (imageExtensions.includes(ext)) {
60
+ try {
61
+ cpSync(srcPath, destPath);
62
+ copied++;
63
+ logger.debug(` ✓ ${entry.name}`);
64
+ } catch (error) {
65
+ logger.warn(` ${entry.name} - ${error.message}`);
66
+ }
67
+ } else {
68
+ logger.debug(` - ${entry.name} (skipped, not an image)`);
276
69
  }
277
70
  }
278
71
  }
72
+ } catch (error) {
73
+ logger.error(`Error processing directory ${dir}: ${error.message}`);
279
74
  }
280
75
  }
281
76
 
77
+ logger.info(`Processing ${srcDir}...`);
282
78
  processDirectory(srcDir, outDir);
283
79
 
284
80
  if (copied > 0) {
285
- logger.info(`📋 Copied ${copied} images without optimization`);
81
+ logger.success(`✅ Copied ${copied} images to ${outDir}`);
286
82
  } else {
287
83
  logger.warn(`⚠️ No images found in ${srcDir}`);
288
84
  }
package/src/build.js CHANGED
@@ -1,4 +1,4 @@
1
- // src/build.js - FIXED VERSION
1
+ // src/build.js - COMPLETELY FIXED VERSION
2
2
  import { join, relative, basename, extname, dirname } from 'path';
3
3
  import { existsSync, mkdirSync, rmSync, cpSync, readdirSync, statSync } from 'fs';
4
4
  import logger from './logger/logger.js';
@@ -13,6 +13,7 @@ export async function buildProduction(options = {}) {
13
13
 
14
14
  logger.bigLog('BUILDING FOR PRODUCTION', { color: 'green' });
15
15
 
16
+ // Clean up old builds
16
17
  if (existsSync(buildDir)) {
17
18
  rmSync(buildDir, { recursive: true });
18
19
  }
@@ -35,7 +36,7 @@ export async function buildProduction(options = {}) {
35
36
 
36
37
  logger.info('Step 1: Compiling for production...');
37
38
  const { routes } = await compileForBuild(root, buildDir, envVars);
38
- logger.success('Production compilation complete');
39
+ logger.success(`Production compilation complete - ${routes.length} routes`);
39
40
 
40
41
  logger.info('Step 2: Building CSS with Lightning CSS...');
41
42
  await buildAllCSS(root, outDir);
@@ -44,8 +45,8 @@ export async function buildProduction(options = {}) {
44
45
  const optimizationTools = await checkOptimizationTools();
45
46
 
46
47
  logger.info('Step 4: Copying and optimizing static assets...');
47
- // FIX 1: Copy images from BOTH src/images/ and public/
48
- await copyAllStaticAssets(root, outDir, optimizationTools.length > 0);
48
+ // SKIP OPTIMIZATION FOR NOW - JUST COPY
49
+ await copyAllStaticAssets(root, outDir, false);
49
50
 
50
51
  logger.info('Step 5: Bundling JavaScript with Bun...');
51
52
  const buildEntry = join(buildDir, 'main.js');
@@ -85,22 +86,33 @@ export async function buildProduction(options = {}) {
85
86
  process.exit(1);
86
87
  }
87
88
 
88
- logger.success('JavaScript bundled with tree-shaking');
89
+ logger.success('JavaScript bundled successfully');
89
90
 
90
- logger.info('Step 6: Generating SEO-optimized HTML files...');
91
- // ✅ FIX 2: Generate HTML for ALL routes including index.html
91
+ // DEBUG: Show what was built
92
+ logger.info('Built outputs:');
93
+ result.outputs.forEach((output, i) => {
94
+ logger.info(` ${i + 1}. ${relative(outDir, output.path)} (${output.kind})`);
95
+ });
96
+
97
+ logger.info('Step 6: Generating HTML files...');
98
+ // ✅ CRITICAL FIX: Generate HTML files
92
99
  await generateProductionHTML(root, outDir, result, routes);
93
100
 
94
- rmSync(buildDir, { recursive: true });
95
- logger.info('Cleaned up .bertuibuild/');
101
+ // Clean up build directory
102
+ if (existsSync(buildDir)) {
103
+ rmSync(buildDir, { recursive: true });
104
+ logger.info('Cleaned up .bertuibuild/');
105
+ }
96
106
 
97
107
  const duration = Date.now() - startTime;
98
108
  logger.success(`✨ Build complete in ${duration}ms`);
99
109
  logger.info(`📦 Output: ${outDir}`);
100
110
 
111
+ // Show build summary
101
112
  logger.table(result.outputs.map(o => ({
102
113
  file: o.path.replace(outDir, ''),
103
- size: `${(o.size / 1024).toFixed(2)} KB`
114
+ size: `${(o.size / 1024).toFixed(2)} KB`,
115
+ type: o.kind
104
116
  })));
105
117
 
106
118
  logger.bigLog('READY TO DEPLOY', { color: 'green' });
@@ -108,7 +120,7 @@ export async function buildProduction(options = {}) {
108
120
  console.log(' Vercel: bunx vercel');
109
121
  console.log(' Netlify: bunx netlify deploy');
110
122
  console.log('\n🔍 Preview locally:\n');
111
- console.log(' bun run preview\n');
123
+ console.log(' cd dist && bun run preview\n');
112
124
 
113
125
  } catch (error) {
114
126
  logger.error(`Build failed: ${error.message}`);
@@ -124,64 +136,33 @@ export async function buildProduction(options = {}) {
124
136
  }
125
137
  }
126
138
 
127
- // ✅ FIX 3: Enhanced asset copying with proper directory structure
128
- // ✅ FIX 3: Enhanced asset copying with proper directory structure
139
+ // ✅ SIMPLE asset copying
129
140
  async function copyAllStaticAssets(root, outDir, optimize = true) {
130
141
  const publicDir = join(root, 'public');
131
142
  const srcImagesDir = join(root, 'src', 'images');
132
143
 
133
- let assetsCopied = 0;
134
- let assetsOptimized = 0;
135
-
136
- logger.info(`🔍 Checking source directories...`);
137
- logger.info(` public/: ${existsSync(publicDir) ? '✅ exists' : '❌ not found'}`);
138
- logger.info(` src/images/: ${existsSync(srcImagesDir) ? '✅ exists' : '❌ not found'}`);
139
-
140
- // Create images directory in dist/
141
- const distImagesDir = join(outDir, 'images');
142
- if (!existsSync(distImagesDir)) {
143
- mkdirSync(distImagesDir, { recursive: true });
144
- }
144
+ // ALWAYS use simple copy for now
145
+ logger.info('Using simple asset copy (optimization disabled)...');
145
146
 
146
147
  // Copy from public/ to root of dist/
147
148
  if (existsSync(publicDir)) {
148
- logger.info('Copying public/ assets...');
149
- if (optimize) {
150
- const result = await optimizeImages(publicDir, outDir);
151
- assetsOptimized += result.optimized;
152
- } else {
153
- assetsCopied += copyImages(publicDir, outDir);
154
- }
149
+ logger.info('📁 Copying public/ directory...');
150
+ copyImages(publicDir, outDir);
155
151
  } else {
156
- logger.info('No public/ directory found, skipping...');
152
+ logger.info('No public/ directory found');
157
153
  }
158
154
 
159
- // ✅ FIX: Copy from src/images/ to dist/images/
155
+ // Copy from src/images/ to dist/images/
160
156
  if (existsSync(srcImagesDir)) {
161
- logger.info(`Copying src/images/ to dist/images/...`);
162
-
163
- // Debug: List files in src/images/
164
- const files = readdirSync(srcImagesDir);
165
- logger.info(`Found ${files.length} items in src/images/: ${files.join(', ')}`);
166
-
167
- if (optimize) {
168
- const result = await optimizeImages(srcImagesDir, distImagesDir);
169
- assetsOptimized += result.optimized;
170
- } else {
171
- assetsCopied += copyImages(srcImagesDir, distImagesDir);
172
- }
173
- } else {
174
- logger.info('No src/images/ directory found, skipping...');
175
- }
176
-
177
- if (optimize && assetsOptimized > 0) {
178
- logger.success(`🎨 Optimized ${assetsOptimized} images with WASM codecs`);
179
- } else if (assetsCopied > 0) {
180
- logger.success(`📋 Copied ${assetsCopied} static assets`);
157
+ logger.info('🖼️ Copying src/images/ to dist/images/...');
158
+ const distImagesDir = join(outDir, 'images');
159
+ mkdirSync(distImagesDir, { recursive: true });
160
+ copyImages(srcImagesDir, distImagesDir);
181
161
  } else {
182
- logger.warn(`⚠️ No static assets found or copied`);
162
+ logger.info('No src/images/ directory found');
183
163
  }
184
164
  }
165
+
185
166
  async function buildAllCSS(root, outDir) {
186
167
  const srcStylesDir = join(root, 'src', 'styles');
187
168
  const stylesOutDir = join(outDir, 'styles');
@@ -569,63 +550,71 @@ function extractMetaFromSource(code) {
569
550
  }
570
551
  }
571
552
 
572
- // ✅ FIX 4: Generate proper HTML files with correct meta tags
553
+ // ✅ CRITICAL FIX: Generate HTML files
573
554
  async function generateProductionHTML(root, outDir, buildResult, routes) {
555
+ if (routes.length === 0) {
556
+ logger.warn('No routes found, skipping HTML generation');
557
+ return;
558
+ }
559
+
560
+ logger.info(`Generating HTML files for ${routes.length} routes...`);
561
+
562
+ // Find main JS bundle
574
563
  const mainBundle = buildResult.outputs.find(o =>
575
564
  o.path.includes('main') && o.kind === 'entry-point'
576
565
  );
577
566
 
578
567
  if (!mainBundle) {
579
- throw new Error('Could not find main bundle in build output');
568
+ logger.error('Could not find main bundle in build output');
569
+ // List all outputs for debugging
570
+ logger.info('Available outputs:');
571
+ buildResult.outputs.forEach((o, i) => {
572
+ logger.info(` ${i + 1}. ${o.path} (${o.kind})`);
573
+ });
574
+ return;
580
575
  }
581
576
 
582
577
  const bundlePath = relative(outDir, mainBundle.path).replace(/\\/g, '/');
583
- logger.info(`Main bundle path: ${bundlePath}`);
584
-
585
- const srcStylesDir = join(root, 'src', 'styles');
586
- let userStylesheets = '';
578
+ logger.info(`Main bundle: ${bundlePath}`);
587
579
 
588
- if (existsSync(srcStylesDir)) {
589
- const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
590
- userStylesheets = cssFiles.map(f =>
591
- ` <link rel="stylesheet" href="/styles/${f.replace('.css', '.min.css')}">`
592
- ).join('\n');
593
- }
594
-
595
- // ✅ Load config for default meta
580
+ // Load config for default meta
596
581
  const { loadConfig } = await import('./config/loadConfig.js');
597
582
  const config = await loadConfig(root);
598
583
  const defaultMeta = config.meta || {};
599
584
 
600
- logger.info('Generating SEO-optimized HTML files...');
601
-
602
- // ✅ FIX: Generate HTML for ALL routes (including dynamic as fallback)
585
+ // Generate HTML for each route
603
586
  for (const route of routes) {
604
- const sourceCode = await Bun.file(route.path).text();
605
- const pageMeta = extractMetaFromSource(sourceCode);
606
- const meta = { ...defaultMeta, ...pageMeta };
607
-
608
- if (pageMeta) {
609
- logger.info(`Extracted meta for ${route.route}: ${JSON.stringify(pageMeta)}`);
610
- }
611
-
612
- const html = generateHTML(meta, route, bundlePath, userStylesheets);
613
-
614
- let htmlPath;
615
- if (route.route === '/') {
616
- htmlPath = join(outDir, 'index.html');
617
- } else {
618
- const routeDir = join(outDir, route.route);
619
- mkdirSync(routeDir, { recursive: true });
620
- htmlPath = join(routeDir, 'index.html');
587
+ try {
588
+ const sourceCode = await Bun.file(route.path).text();
589
+ const pageMeta = extractMetaFromSource(sourceCode);
590
+ const meta = { ...defaultMeta, ...pageMeta };
591
+
592
+ const html = generateHTML(meta, route, bundlePath);
593
+
594
+ let htmlPath;
595
+ if (route.route === '/') {
596
+ htmlPath = join(outDir, 'index.html');
597
+ } else {
598
+ // Create directory for the route
599
+ const routeDir = join(outDir, route.route.slice(1)); // Remove leading slash
600
+ mkdirSync(routeDir, { recursive: true });
601
+ htmlPath = join(routeDir, 'index.html');
602
+ }
603
+
604
+ await Bun.write(htmlPath, html);
605
+ logger.success(`Generated: ${route.route === '/' ? 'index.html' : route.route + '/index.html'}`);
606
+ } catch (error) {
607
+ logger.error(`Failed to generate HTML for ${route.route}: ${error.message}`);
621
608
  }
622
-
623
- await Bun.write(htmlPath, html);
624
- logger.success(`Generated ${route.route === '/' ? 'index.html' : route.route + '/index.html'}`);
625
609
  }
626
610
  }
627
611
 
628
- function generateHTML(meta, route, bundlePath, userStylesheets) {
612
+ function generateHTML(meta, route, bundlePath) {
613
+ const cssFiles = ['global.min.css', 'home.min.css'];
614
+ const stylesheets = cssFiles.map(css =>
615
+ ` <link rel="stylesheet" href="/styles/${css}">`
616
+ ).join('\n');
617
+
629
618
  return `<!DOCTYPE html>
630
619
  <html lang="${meta.lang || 'en'}">
631
620
  <head>
@@ -636,23 +625,10 @@ function generateHTML(meta, route, bundlePath, userStylesheets) {
636
625
  <meta name="description" content="${meta.description || 'Built with BertUI - Lightning fast React development'}">
637
626
  ${meta.keywords ? `<meta name="keywords" content="${meta.keywords}">` : ''}
638
627
  ${meta.author ? `<meta name="author" content="${meta.author}">` : ''}
639
- ${meta.themeColor ? `<meta name="theme-color" content="${meta.themeColor}">` : ''}
640
-
641
- <meta property="og:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
642
- <meta property="og:description" content="${meta.ogDescription || meta.description || 'Built with BertUI'}">
643
- ${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
644
- <meta property="og:type" content="website">
645
- <meta property="og:url" content="${route.route}">
646
-
647
- <meta name="twitter:card" content="summary_large_image">
648
- <meta name="twitter:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
649
- <meta name="twitter:description" content="${meta.ogDescription || meta.description || 'Built with BertUI'}">
650
- ${meta.ogImage ? `<meta name="twitter:image" content="${meta.ogImage}">` : ''}
651
628
 
652
629
  <link rel="icon" type="image/svg+xml" href="/favicon.svg">
653
- <link rel="canonical" href="${route.route}">
654
630
 
655
- ${userStylesheets}
631
+ ${stylesheets}
656
632
 
657
633
  <script type="importmap">
658
634
  {