bertui 0.4.4 → 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 +1 -1
- package/src/build/image-optimizer.js +61 -190
- package/src/build.js +83 -90
package/package.json
CHANGED
|
@@ -1,216 +1,87 @@
|
|
|
1
|
-
// bertui/src/build/image-optimizer.js -
|
|
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
|
-
*
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
logger.info('🖼️ Optimizing images with WASM codecs...');
|
|
50
|
-
|
|
51
|
-
async function processDirectory(dir, targetDir) {
|
|
52
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
53
|
-
|
|
54
|
-
for (const entry of entries) {
|
|
55
|
-
const srcPath = join(dir, entry.name);
|
|
56
|
-
const destPath = join(targetDir, entry.name);
|
|
57
|
-
|
|
58
|
-
if (entry.isDirectory()) {
|
|
59
|
-
if (!existsSync(destPath)) {
|
|
60
|
-
mkdirSync(destPath, { recursive: true });
|
|
61
|
-
}
|
|
62
|
-
await processDirectory(srcPath, destPath);
|
|
63
|
-
} else if (entry.isFile()) {
|
|
64
|
-
const ext = extname(entry.name).toLowerCase();
|
|
65
|
-
|
|
66
|
-
if (imageExtensions.includes(ext)) {
|
|
67
|
-
try {
|
|
68
|
-
const result = await optimizeImage(srcPath, destPath);
|
|
69
|
-
if (result) {
|
|
70
|
-
optimized++;
|
|
71
|
-
totalSaved += result.saved;
|
|
72
|
-
const savedPercent = ((result.saved / result.originalSize) * 100).toFixed(1);
|
|
73
|
-
logger.debug(
|
|
74
|
-
`✨ ${entry.name}: ${(result.originalSize / 1024).toFixed(1)}KB → ${(result.newSize / 1024).toFixed(1)}KB (-${savedPercent}%)`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
} catch (error) {
|
|
78
|
-
logger.warn(`⚠️ Failed to optimize ${entry.name}: ${error.message}`);
|
|
79
|
-
// Fallback: just copy the file
|
|
80
|
-
cpSync(srcPath, destPath);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
await processDirectory(srcDir, outDir);
|
|
88
|
-
|
|
89
|
-
if (optimized > 0) {
|
|
90
|
-
logger.success(
|
|
91
|
-
`✅ Optimized ${optimized} images (saved ${(totalSaved / 1024).toFixed(2)}KB total)`
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { optimized, saved: totalSaved };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Optimize a single image using WASM codecs
|
|
100
|
-
*/
|
|
101
|
-
async function optimizeImage(srcPath, destPath) {
|
|
102
|
-
const ext = extname(srcPath).toLowerCase();
|
|
103
|
-
const originalFile = Bun.file(srcPath);
|
|
104
|
-
const originalSize = originalFile.size;
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
// For SVG and GIF, just copy (no optimization needed/supported)
|
|
108
|
-
if (ext === '.svg' || ext === '.gif') {
|
|
109
|
-
cpSync(srcPath, destPath);
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Read the original image
|
|
114
|
-
const originalBuffer = await originalFile.arrayBuffer();
|
|
115
|
-
|
|
116
|
-
let optimizedBuffer;
|
|
117
|
-
|
|
118
|
-
if (ext === '.png') {
|
|
119
|
-
await initializePNG();
|
|
120
|
-
|
|
121
|
-
// Decode → Re-encode with compression
|
|
122
|
-
const imageData = await pngDecode(originalBuffer);
|
|
123
|
-
|
|
124
|
-
// Encode with oxipng-level compression (quality 85, similar to oxipng -o 2)
|
|
125
|
-
optimizedBuffer = await pngEncode(imageData, {
|
|
126
|
-
quality: 85,
|
|
127
|
-
effort: 2 // 0-10, higher = better compression but slower
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
} else if (ext === '.jpg' || ext === '.jpeg') {
|
|
131
|
-
await initializeJPEG();
|
|
132
|
-
|
|
133
|
-
// Decode → Re-encode with quality 85 (mozjpeg-like quality)
|
|
134
|
-
const imageData = await jpegDecode(originalBuffer);
|
|
135
|
-
optimizedBuffer = await jpegEncode(imageData, { quality: 85 });
|
|
136
|
-
|
|
137
|
-
} else if (ext === '.webp') {
|
|
138
|
-
await initializeWebP();
|
|
139
|
-
|
|
140
|
-
// WebP optimization
|
|
141
|
-
const imageData = await webpDecode(originalBuffer);
|
|
142
|
-
optimizedBuffer = await webpEncode(imageData, { quality: 85 });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Only save if we actually reduced the size
|
|
146
|
-
if (optimizedBuffer && optimizedBuffer.byteLength < originalSize) {
|
|
147
|
-
await Bun.write(destPath, optimizedBuffer);
|
|
148
|
-
const saved = originalSize - optimizedBuffer.byteLength;
|
|
149
|
-
return { saved, originalSize, newSize: optimizedBuffer.byteLength };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// If optimization didn't help, just copy the original
|
|
153
|
-
cpSync(srcPath, destPath);
|
|
154
|
-
return null;
|
|
155
|
-
|
|
156
|
-
} catch (error) {
|
|
157
|
-
// If anything fails, just copy the original
|
|
158
|
-
logger.warn(`Optimization failed for ${srcPath.split('/').pop()}, copying original`);
|
|
159
|
-
cpSync(srcPath, destPath);
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
10
|
+
logger.info(`📋 Copying images from ${srcDir} to ${outDir}...`);
|
|
11
|
+
const copied = copyImages(srcDir, outDir);
|
|
12
|
+
return { optimized: 0, saved: 0, copied };
|
|
162
13
|
}
|
|
163
14
|
|
|
164
|
-
/**
|
|
165
|
-
* Check if optimization is available (always true with WASM! 🎉)
|
|
166
|
-
*/
|
|
167
15
|
export async function checkOptimizationTools() {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
await import('@jsquash/png');
|
|
171
|
-
await import('@jsquash/jpeg');
|
|
172
|
-
await import('@jsquash/webp');
|
|
173
|
-
|
|
174
|
-
logger.success('✅ WASM image optimization available');
|
|
175
|
-
logger.info('📦 Using @jsquash (zero OS dependencies!)');
|
|
176
|
-
return ['png', 'jpeg', 'webp'];
|
|
177
|
-
} catch (error) {
|
|
178
|
-
logger.error('❌ WASM codecs not installed. Run: bun add @jsquash/png @jsquash/jpeg @jsquash/webp');
|
|
179
|
-
return [];
|
|
180
|
-
}
|
|
16
|
+
logger.info('📋 Image optimization disabled (simple mode)');
|
|
17
|
+
return [];
|
|
181
18
|
}
|
|
182
19
|
|
|
183
|
-
/**
|
|
184
|
-
* Copy images without optimization (fallback)
|
|
185
|
-
*/
|
|
186
20
|
export function copyImages(srcDir, outDir) {
|
|
187
|
-
const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.avif'];
|
|
21
|
+
const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.avif', '.ico'];
|
|
188
22
|
let copied = 0;
|
|
189
23
|
|
|
190
|
-
|
|
191
|
-
|
|
24
|
+
// Check if source directory exists
|
|
25
|
+
if (!existsSync(srcDir)) {
|
|
26
|
+
logger.warn(`⚠️ Source directory not found: ${srcDir}`);
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
192
29
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
30
|
+
// Create output directory if it doesn't exist
|
|
31
|
+
if (!existsSync(outDir)) {
|
|
32
|
+
mkdirSync(outDir, { recursive: true });
|
|
33
|
+
logger.info(`Created directory: ${outDir}`);
|
|
34
|
+
}
|
|
196
35
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
36
|
+
function processDirectory(dir, targetDir) {
|
|
37
|
+
try {
|
|
38
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
39
|
+
|
|
40
|
+
if (entries.length === 0) {
|
|
41
|
+
logger.warn(`Directory empty: ${dir}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const srcPath = join(dir, entry.name);
|
|
47
|
+
const destPath = join(targetDir, entry.name);
|
|
204
48
|
|
|
205
|
-
if (
|
|
206
|
-
|
|
207
|
-
|
|
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();
|
|
58
|
+
|
|
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)`);
|
|
69
|
+
}
|
|
208
70
|
}
|
|
209
71
|
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
logger.error(`Error processing directory ${dir}: ${error.message}`);
|
|
210
74
|
}
|
|
211
75
|
}
|
|
212
76
|
|
|
77
|
+
logger.info(`Processing ${srcDir}...`);
|
|
213
78
|
processDirectory(srcDir, outDir);
|
|
214
|
-
|
|
79
|
+
|
|
80
|
+
if (copied > 0) {
|
|
81
|
+
logger.success(`✅ Copied ${copied} images to ${outDir}`);
|
|
82
|
+
} else {
|
|
83
|
+
logger.warn(`⚠️ No images found in ${srcDir}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
215
86
|
return copied;
|
|
216
87
|
}
|
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(
|
|
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
|
-
//
|
|
48
|
-
await copyAllStaticAssets(root, outDir,
|
|
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
|
|
89
|
+
logger.success('JavaScript bundled successfully');
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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,44 +136,30 @@ export async function buildProduction(options = {}) {
|
|
|
124
136
|
}
|
|
125
137
|
}
|
|
126
138
|
|
|
127
|
-
// ✅
|
|
139
|
+
// ✅ SIMPLE asset copying
|
|
128
140
|
async function copyAllStaticAssets(root, outDir, optimize = true) {
|
|
129
141
|
const publicDir = join(root, 'public');
|
|
130
142
|
const srcImagesDir = join(root, 'src', 'images');
|
|
131
143
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// Create images directory in dist/
|
|
136
|
-
const distImagesDir = join(outDir, 'images');
|
|
137
|
-
mkdirSync(distImagesDir, { recursive: true });
|
|
144
|
+
// ALWAYS use simple copy for now
|
|
145
|
+
logger.info('Using simple asset copy (optimization disabled)...');
|
|
138
146
|
|
|
139
147
|
// Copy from public/ to root of dist/
|
|
140
148
|
if (existsSync(publicDir)) {
|
|
141
|
-
logger.info('Copying public/
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
} else {
|
|
146
|
-
assetsCopied += copyImages(publicDir, outDir);
|
|
147
|
-
}
|
|
149
|
+
logger.info('📁 Copying public/ directory...');
|
|
150
|
+
copyImages(publicDir, outDir);
|
|
151
|
+
} else {
|
|
152
|
+
logger.info('No public/ directory found');
|
|
148
153
|
}
|
|
149
154
|
|
|
150
|
-
//
|
|
155
|
+
// Copy from src/images/ to dist/images/
|
|
151
156
|
if (existsSync(srcImagesDir)) {
|
|
152
|
-
logger.info('Copying src/images/ to dist/images/...');
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
} else {
|
|
157
|
-
assetsCopied += copyImages(srcImagesDir, distImagesDir);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (optimize && assetsOptimized > 0) {
|
|
162
|
-
logger.success(`🎨 Optimized ${assetsOptimized} images with WASM codecs`);
|
|
157
|
+
logger.info('🖼️ Copying src/images/ to dist/images/...');
|
|
158
|
+
const distImagesDir = join(outDir, 'images');
|
|
159
|
+
mkdirSync(distImagesDir, { recursive: true });
|
|
160
|
+
copyImages(srcImagesDir, distImagesDir);
|
|
163
161
|
} else {
|
|
164
|
-
logger.
|
|
162
|
+
logger.info('No src/images/ directory found');
|
|
165
163
|
}
|
|
166
164
|
}
|
|
167
165
|
|
|
@@ -552,63 +550,71 @@ function extractMetaFromSource(code) {
|
|
|
552
550
|
}
|
|
553
551
|
}
|
|
554
552
|
|
|
555
|
-
// ✅ FIX
|
|
553
|
+
// ✅ CRITICAL FIX: Generate HTML files
|
|
556
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
|
|
557
563
|
const mainBundle = buildResult.outputs.find(o =>
|
|
558
564
|
o.path.includes('main') && o.kind === 'entry-point'
|
|
559
565
|
);
|
|
560
566
|
|
|
561
567
|
if (!mainBundle) {
|
|
562
|
-
|
|
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;
|
|
563
575
|
}
|
|
564
576
|
|
|
565
577
|
const bundlePath = relative(outDir, mainBundle.path).replace(/\\/g, '/');
|
|
566
|
-
logger.info(`Main bundle
|
|
567
|
-
|
|
568
|
-
const srcStylesDir = join(root, 'src', 'styles');
|
|
569
|
-
let userStylesheets = '';
|
|
570
|
-
|
|
571
|
-
if (existsSync(srcStylesDir)) {
|
|
572
|
-
const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
|
|
573
|
-
userStylesheets = cssFiles.map(f =>
|
|
574
|
-
` <link rel="stylesheet" href="/styles/${f.replace('.css', '.min.css')}">`
|
|
575
|
-
).join('\n');
|
|
576
|
-
}
|
|
578
|
+
logger.info(`Main bundle: ${bundlePath}`);
|
|
577
579
|
|
|
578
|
-
//
|
|
580
|
+
// Load config for default meta
|
|
579
581
|
const { loadConfig } = await import('./config/loadConfig.js');
|
|
580
582
|
const config = await loadConfig(root);
|
|
581
583
|
const defaultMeta = config.meta || {};
|
|
582
584
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
// ✅ FIX: Generate HTML for ALL routes (including dynamic as fallback)
|
|
585
|
+
// Generate HTML for each route
|
|
586
586
|
for (const route of routes) {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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}`);
|
|
604
608
|
}
|
|
605
|
-
|
|
606
|
-
await Bun.write(htmlPath, html);
|
|
607
|
-
logger.success(`Generated ${route.route === '/' ? 'index.html' : route.route + '/index.html'}`);
|
|
608
609
|
}
|
|
609
610
|
}
|
|
610
611
|
|
|
611
|
-
function generateHTML(meta, route, bundlePath
|
|
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
|
+
|
|
612
618
|
return `<!DOCTYPE html>
|
|
613
619
|
<html lang="${meta.lang || 'en'}">
|
|
614
620
|
<head>
|
|
@@ -619,23 +625,10 @@ function generateHTML(meta, route, bundlePath, userStylesheets) {
|
|
|
619
625
|
<meta name="description" content="${meta.description || 'Built with BertUI - Lightning fast React development'}">
|
|
620
626
|
${meta.keywords ? `<meta name="keywords" content="${meta.keywords}">` : ''}
|
|
621
627
|
${meta.author ? `<meta name="author" content="${meta.author}">` : ''}
|
|
622
|
-
${meta.themeColor ? `<meta name="theme-color" content="${meta.themeColor}">` : ''}
|
|
623
|
-
|
|
624
|
-
<meta property="og:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
|
|
625
|
-
<meta property="og:description" content="${meta.ogDescription || meta.description || 'Built with BertUI'}">
|
|
626
|
-
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
627
|
-
<meta property="og:type" content="website">
|
|
628
|
-
<meta property="og:url" content="${route.route}">
|
|
629
|
-
|
|
630
|
-
<meta name="twitter:card" content="summary_large_image">
|
|
631
|
-
<meta name="twitter:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
|
|
632
|
-
<meta name="twitter:description" content="${meta.ogDescription || meta.description || 'Built with BertUI'}">
|
|
633
|
-
${meta.ogImage ? `<meta name="twitter:image" content="${meta.ogImage}">` : ''}
|
|
634
628
|
|
|
635
629
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
636
|
-
<link rel="canonical" href="${route.route}">
|
|
637
630
|
|
|
638
|
-
${
|
|
631
|
+
${stylesheets}
|
|
639
632
|
|
|
640
633
|
<script type="importmap">
|
|
641
634
|
{
|