bertui 0.4.2 → 0.4.5
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 +88 -13
- package/src/build.js +56 -41
- package/src/server/dev-server.js +26 -35
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// bertui/src/build/image-optimizer.js -
|
|
1
|
+
// bertui/src/build/image-optimizer.js - FIXED 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';
|
|
@@ -46,7 +46,18 @@ export async function optimizeImages(srcDir, outDir) {
|
|
|
46
46
|
let optimized = 0;
|
|
47
47
|
let totalSaved = 0;
|
|
48
48
|
|
|
49
|
-
logger.info(
|
|
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
|
+
}
|
|
50
61
|
|
|
51
62
|
async function processDirectory(dir, targetDir) {
|
|
52
63
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -56,10 +67,12 @@ export async function optimizeImages(srcDir, outDir) {
|
|
|
56
67
|
const destPath = join(targetDir, entry.name);
|
|
57
68
|
|
|
58
69
|
if (entry.isDirectory()) {
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
// Create subdirectory in target
|
|
71
|
+
const subDestPath = join(targetDir, entry.name);
|
|
72
|
+
if (!existsSync(subDestPath)) {
|
|
73
|
+
mkdirSync(subDestPath, { recursive: true });
|
|
61
74
|
}
|
|
62
|
-
await processDirectory(srcPath,
|
|
75
|
+
await processDirectory(srcPath, subDestPath);
|
|
63
76
|
} else if (entry.isFile()) {
|
|
64
77
|
const ext = extname(entry.name).toLowerCase();
|
|
65
78
|
|
|
@@ -77,7 +90,12 @@ export async function optimizeImages(srcDir, outDir) {
|
|
|
77
90
|
} catch (error) {
|
|
78
91
|
logger.warn(`⚠️ Failed to optimize ${entry.name}: ${error.message}`);
|
|
79
92
|
// Fallback: just copy the file
|
|
80
|
-
|
|
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
|
+
}
|
|
81
99
|
}
|
|
82
100
|
}
|
|
83
101
|
}
|
|
@@ -90,17 +108,45 @@ export async function optimizeImages(srcDir, outDir) {
|
|
|
90
108
|
logger.success(
|
|
91
109
|
`✅ Optimized ${optimized} images (saved ${(totalSaved / 1024).toFixed(2)}KB total)`
|
|
92
110
|
);
|
|
111
|
+
} else {
|
|
112
|
+
logger.info(`📋 No images optimized (copied ${countFilesInDir(outDir)} files)`);
|
|
93
113
|
}
|
|
94
114
|
|
|
95
115
|
return { optimized, saved: totalSaved };
|
|
96
116
|
}
|
|
97
117
|
|
|
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
|
+
|
|
98
138
|
/**
|
|
99
139
|
* Optimize a single image using WASM codecs
|
|
100
140
|
*/
|
|
101
141
|
async function optimizeImage(srcPath, destPath) {
|
|
102
142
|
const ext = extname(srcPath).toLowerCase();
|
|
103
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
|
+
|
|
104
150
|
const originalSize = originalFile.size;
|
|
105
151
|
|
|
106
152
|
try {
|
|
@@ -140,6 +186,10 @@ async function optimizeImage(srcPath, destPath) {
|
|
|
140
186
|
// WebP optimization
|
|
141
187
|
const imageData = await webpDecode(originalBuffer);
|
|
142
188
|
optimizedBuffer = await webpEncode(imageData, { quality: 85 });
|
|
189
|
+
} else {
|
|
190
|
+
// Unsupported format, just copy
|
|
191
|
+
cpSync(srcPath, destPath);
|
|
192
|
+
return null;
|
|
143
193
|
}
|
|
144
194
|
|
|
145
195
|
// Only save if we actually reduced the size
|
|
@@ -184,9 +234,20 @@ export async function checkOptimizationTools() {
|
|
|
184
234
|
* Copy images without optimization (fallback)
|
|
185
235
|
*/
|
|
186
236
|
export function copyImages(srcDir, outDir) {
|
|
187
|
-
const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.avif'];
|
|
237
|
+
const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.avif', '.ico'];
|
|
188
238
|
let copied = 0;
|
|
189
239
|
|
|
240
|
+
// Check if source directory exists
|
|
241
|
+
if (!existsSync(srcDir)) {
|
|
242
|
+
logger.warn(`⚠️ Source directory not found: ${srcDir}`);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Create output directory if it doesn't exist
|
|
247
|
+
if (!existsSync(outDir)) {
|
|
248
|
+
mkdirSync(outDir, { recursive: true });
|
|
249
|
+
}
|
|
250
|
+
|
|
190
251
|
function processDirectory(dir, targetDir) {
|
|
191
252
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
192
253
|
|
|
@@ -195,22 +256,36 @@ export function copyImages(srcDir, outDir) {
|
|
|
195
256
|
const destPath = join(targetDir, entry.name);
|
|
196
257
|
|
|
197
258
|
if (entry.isDirectory()) {
|
|
198
|
-
|
|
199
|
-
|
|
259
|
+
// Create subdirectory in target
|
|
260
|
+
const subDestPath = join(targetDir, entry.name);
|
|
261
|
+
if (!existsSync(subDestPath)) {
|
|
262
|
+
mkdirSync(subDestPath, { recursive: true });
|
|
200
263
|
}
|
|
201
|
-
|
|
264
|
+
// FIXED: Use destPath, not targetDir
|
|
265
|
+
processDirectory(srcPath, subDestPath);
|
|
202
266
|
} else if (entry.isFile()) {
|
|
203
267
|
const ext = extname(entry.name).toLowerCase();
|
|
204
268
|
|
|
205
269
|
if (imageExtensions.includes(ext)) {
|
|
206
|
-
|
|
207
|
-
|
|
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}`);
|
|
276
|
+
}
|
|
208
277
|
}
|
|
209
278
|
}
|
|
210
279
|
}
|
|
211
280
|
}
|
|
212
281
|
|
|
213
282
|
processDirectory(srcDir, outDir);
|
|
214
|
-
|
|
283
|
+
|
|
284
|
+
if (copied > 0) {
|
|
285
|
+
logger.info(`📋 Copied ${copied} images without optimization`);
|
|
286
|
+
} else {
|
|
287
|
+
logger.warn(`⚠️ No images found in ${srcDir}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
215
290
|
return copied;
|
|
216
291
|
}
|
package/src/build.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// src/build.js - FIXED VERSION
|
|
1
2
|
import { join, relative, basename, extname, dirname } from 'path';
|
|
2
3
|
import { existsSync, mkdirSync, rmSync, cpSync, readdirSync, statSync } from 'fs';
|
|
3
4
|
import logger from './logger/logger.js';
|
|
@@ -39,18 +40,12 @@ export async function buildProduction(options = {}) {
|
|
|
39
40
|
logger.info('Step 2: Building CSS with Lightning CSS...');
|
|
40
41
|
await buildAllCSS(root, outDir);
|
|
41
42
|
|
|
42
|
-
// ✅ NEW: Check if image optimization is available
|
|
43
43
|
logger.info('Step 3: Checking image optimization tools...');
|
|
44
44
|
const optimizationTools = await checkOptimizationTools();
|
|
45
45
|
|
|
46
46
|
logger.info('Step 4: Copying and optimizing static assets...');
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
await copyAllStaticAssets(root, outDir, true);
|
|
50
|
-
} else {
|
|
51
|
-
// Fallback: just copy images
|
|
52
|
-
await copyAllStaticAssets(root, outDir, false);
|
|
53
|
-
}
|
|
47
|
+
// ✅ FIX 1: Copy images from BOTH src/images/ and public/
|
|
48
|
+
await copyAllStaticAssets(root, outDir, optimizationTools.length > 0);
|
|
54
49
|
|
|
55
50
|
logger.info('Step 5: Bundling JavaScript with Bun...');
|
|
56
51
|
const buildEntry = join(buildDir, 'main.js');
|
|
@@ -75,9 +70,6 @@ export async function buildProduction(options = {}) {
|
|
|
75
70
|
external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime'],
|
|
76
71
|
define: {
|
|
77
72
|
'process.env.NODE_ENV': '"production"',
|
|
78
|
-
'process.env.PUBLIC_APP_NAME': JSON.stringify(envVars.PUBLIC_APP_NAME || 'BertUI App'),
|
|
79
|
-
'process.env.PUBLIC_API_URL': JSON.stringify(envVars.PUBLIC_API_URL || ''),
|
|
80
|
-
'process.env.PUBLIC_USERNAME': JSON.stringify(envVars.PUBLIC_USERNAME || ''),
|
|
81
73
|
...Object.fromEntries(
|
|
82
74
|
Object.entries(envVars).map(([key, value]) => [
|
|
83
75
|
`process.env.${key}`,
|
|
@@ -96,6 +88,7 @@ export async function buildProduction(options = {}) {
|
|
|
96
88
|
logger.success('JavaScript bundled with tree-shaking');
|
|
97
89
|
|
|
98
90
|
logger.info('Step 6: Generating SEO-optimized HTML files...');
|
|
91
|
+
// ✅ FIX 2: Generate HTML for ALL routes including index.html
|
|
99
92
|
await generateProductionHTML(root, outDir, result, routes);
|
|
100
93
|
|
|
101
94
|
rmSync(buildDir, { recursive: true });
|
|
@@ -131,44 +124,64 @@ export async function buildProduction(options = {}) {
|
|
|
131
124
|
}
|
|
132
125
|
}
|
|
133
126
|
|
|
134
|
-
// ✅
|
|
127
|
+
// ✅ FIX 3: Enhanced asset copying with proper directory structure
|
|
128
|
+
// ✅ FIX 3: Enhanced asset copying with proper directory structure
|
|
135
129
|
async function copyAllStaticAssets(root, outDir, optimize = true) {
|
|
136
130
|
const publicDir = join(root, 'public');
|
|
137
|
-
const
|
|
131
|
+
const srcImagesDir = join(root, 'src', 'images');
|
|
138
132
|
|
|
139
133
|
let assetsCopied = 0;
|
|
140
134
|
let assetsOptimized = 0;
|
|
141
135
|
|
|
142
|
-
|
|
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
|
+
}
|
|
145
|
+
|
|
146
|
+
// Copy from public/ to root of dist/
|
|
143
147
|
if (existsSync(publicDir)) {
|
|
148
|
+
logger.info('Copying public/ assets...');
|
|
144
149
|
if (optimize) {
|
|
145
150
|
const result = await optimizeImages(publicDir, outDir);
|
|
146
151
|
assetsOptimized += result.optimized;
|
|
147
152
|
} else {
|
|
148
153
|
assetsCopied += copyImages(publicDir, outDir);
|
|
149
154
|
}
|
|
155
|
+
} else {
|
|
156
|
+
logger.info('No public/ directory found, skipping...');
|
|
150
157
|
}
|
|
151
158
|
|
|
152
|
-
//
|
|
153
|
-
if (existsSync(
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
// ✅ FIX: Copy from src/images/ to dist/images/
|
|
160
|
+
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(', ')}`);
|
|
156
166
|
|
|
157
167
|
if (optimize) {
|
|
158
|
-
const result = await optimizeImages(
|
|
168
|
+
const result = await optimizeImages(srcImagesDir, distImagesDir);
|
|
159
169
|
assetsOptimized += result.optimized;
|
|
160
170
|
} else {
|
|
161
|
-
assetsCopied += copyImages(
|
|
171
|
+
assetsCopied += copyImages(srcImagesDir, distImagesDir);
|
|
162
172
|
}
|
|
173
|
+
} else {
|
|
174
|
+
logger.info('No src/images/ directory found, skipping...');
|
|
163
175
|
}
|
|
164
176
|
|
|
165
177
|
if (optimize && assetsOptimized > 0) {
|
|
166
178
|
logger.success(`🎨 Optimized ${assetsOptimized} images with WASM codecs`);
|
|
167
|
-
} else {
|
|
179
|
+
} else if (assetsCopied > 0) {
|
|
168
180
|
logger.success(`📋 Copied ${assetsCopied} static assets`);
|
|
181
|
+
} else {
|
|
182
|
+
logger.warn(`⚠️ No static assets found or copied`);
|
|
169
183
|
}
|
|
170
184
|
}
|
|
171
|
-
|
|
172
185
|
async function buildAllCSS(root, outDir) {
|
|
173
186
|
const srcStylesDir = join(root, 'src', 'styles');
|
|
174
187
|
const stylesOutDir = join(outDir, 'styles');
|
|
@@ -556,6 +569,7 @@ function extractMetaFromSource(code) {
|
|
|
556
569
|
}
|
|
557
570
|
}
|
|
558
571
|
|
|
572
|
+
// ✅ FIX 4: Generate proper HTML files with correct meta tags
|
|
559
573
|
async function generateProductionHTML(root, outDir, buildResult, routes) {
|
|
560
574
|
const mainBundle = buildResult.outputs.find(o =>
|
|
561
575
|
o.path.includes('main') && o.kind === 'entry-point'
|
|
@@ -578,18 +592,15 @@ async function generateProductionHTML(root, outDir, buildResult, routes) {
|
|
|
578
592
|
).join('\n');
|
|
579
593
|
}
|
|
580
594
|
|
|
595
|
+
// ✅ Load config for default meta
|
|
581
596
|
const { loadConfig } = await import('./config/loadConfig.js');
|
|
582
597
|
const config = await loadConfig(root);
|
|
583
598
|
const defaultMeta = config.meta || {};
|
|
584
599
|
|
|
585
600
|
logger.info('Generating SEO-optimized HTML files...');
|
|
586
601
|
|
|
602
|
+
// ✅ FIX: Generate HTML for ALL routes (including dynamic as fallback)
|
|
587
603
|
for (const route of routes) {
|
|
588
|
-
if (route.type === 'dynamic') {
|
|
589
|
-
logger.info(`Skipping dynamic route: ${route.route}`);
|
|
590
|
-
continue;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
604
|
const sourceCode = await Bun.file(route.path).text();
|
|
594
605
|
const pageMeta = extractMetaFromSource(sourceCode);
|
|
595
606
|
const meta = { ...defaultMeta, ...pageMeta };
|
|
@@ -598,7 +609,24 @@ async function generateProductionHTML(root, outDir, buildResult, routes) {
|
|
|
598
609
|
logger.info(`Extracted meta for ${route.route}: ${JSON.stringify(pageMeta)}`);
|
|
599
610
|
}
|
|
600
611
|
|
|
601
|
-
const html =
|
|
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');
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
await Bun.write(htmlPath, html);
|
|
624
|
+
logger.success(`Generated ${route.route === '/' ? 'index.html' : route.route + '/index.html'}`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function generateHTML(meta, route, bundlePath, userStylesheets) {
|
|
629
|
+
return `<!DOCTYPE html>
|
|
602
630
|
<html lang="${meta.lang || 'en'}">
|
|
603
631
|
<head>
|
|
604
632
|
<meta charset="UTF-8">
|
|
@@ -642,17 +670,4 @@ ${userStylesheets}
|
|
|
642
670
|
<script type="module" src="/${bundlePath}"></script>
|
|
643
671
|
</body>
|
|
644
672
|
</html>`;
|
|
645
|
-
|
|
646
|
-
let htmlPath;
|
|
647
|
-
if (route.route === '/') {
|
|
648
|
-
htmlPath = join(outDir, 'index.html');
|
|
649
|
-
} else {
|
|
650
|
-
const routeDir = join(outDir, route.route);
|
|
651
|
-
mkdirSync(routeDir, { recursive: true });
|
|
652
|
-
htmlPath = join(routeDir, 'index.html');
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
await Bun.write(htmlPath, html);
|
|
656
|
-
logger.success(`Generated ${route.route === '/' ? 'index.html' : route.route + '/index.html'} with meta: ${meta.title || 'default'}`);
|
|
657
|
-
}
|
|
658
673
|
}
|
package/src/server/dev-server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/server/dev-server.js - FIXED
|
|
1
|
+
// src/server/dev-server.js - FIXED IMAGE SERVING
|
|
2
2
|
import { Elysia } from 'elysia';
|
|
3
3
|
import { watch } from 'fs';
|
|
4
4
|
import { join, extname } from 'path';
|
|
@@ -13,6 +13,7 @@ export async function startDevServer(options = {}) {
|
|
|
13
13
|
const compiledDir = join(root, '.bertui', 'compiled');
|
|
14
14
|
const stylesDir = join(root, '.bertui', 'styles');
|
|
15
15
|
const srcDir = join(root, 'src');
|
|
16
|
+
const publicDir = join(root, 'public');
|
|
16
17
|
|
|
17
18
|
const config = await loadConfig(root);
|
|
18
19
|
|
|
@@ -30,10 +31,10 @@ export async function startDevServer(options = {}) {
|
|
|
30
31
|
return serveHTML(root, hasRouter, config);
|
|
31
32
|
})
|
|
32
33
|
|
|
33
|
-
// ✅
|
|
34
|
+
// ✅ FIX: Serve images from src/images/ (CRITICAL)
|
|
34
35
|
.get('/images/*', async ({ params, set }) => {
|
|
35
|
-
const
|
|
36
|
-
const filepath = join(
|
|
36
|
+
const srcImagesDir = join(srcDir, 'images');
|
|
37
|
+
const filepath = join(srcImagesDir, params['*']);
|
|
37
38
|
const file = Bun.file(filepath);
|
|
38
39
|
|
|
39
40
|
if (!await file.exists()) {
|
|
@@ -47,12 +48,27 @@ export async function startDevServer(options = {}) {
|
|
|
47
48
|
return new Response(file, {
|
|
48
49
|
headers: {
|
|
49
50
|
'Content-Type': contentType,
|
|
50
|
-
'Cache-Control': '
|
|
51
|
+
'Cache-Control': 'no-cache' // Dev server = no cache
|
|
51
52
|
}
|
|
52
53
|
});
|
|
53
54
|
})
|
|
54
55
|
|
|
55
|
-
// ✅
|
|
56
|
+
// ✅ Serve from public/ directory
|
|
57
|
+
.get('/public/*', async ({ params, set }) => {
|
|
58
|
+
const filepath = join(publicDir, params['*']);
|
|
59
|
+
const file = Bun.file(filepath);
|
|
60
|
+
|
|
61
|
+
if (!await file.exists()) {
|
|
62
|
+
set.status = 404;
|
|
63
|
+
return 'File not found';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return new Response(file, {
|
|
67
|
+
headers: { 'Cache-Control': 'no-cache' }
|
|
68
|
+
});
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// ✅ Generic asset serving
|
|
56
72
|
.get('/assets/*', async ({ params, set }) => {
|
|
57
73
|
const filepath = join(srcDir, params['*']);
|
|
58
74
|
const file = Bun.file(filepath);
|
|
@@ -68,7 +84,7 @@ export async function startDevServer(options = {}) {
|
|
|
68
84
|
return new Response(file, {
|
|
69
85
|
headers: {
|
|
70
86
|
'Content-Type': contentType,
|
|
71
|
-
'Cache-Control': '
|
|
87
|
+
'Cache-Control': 'no-cache'
|
|
72
88
|
}
|
|
73
89
|
});
|
|
74
90
|
})
|
|
@@ -108,16 +124,6 @@ export async function startDevServer(options = {}) {
|
|
|
108
124
|
}
|
|
109
125
|
}
|
|
110
126
|
|
|
111
|
-
if (path.startsWith('public/')) {
|
|
112
|
-
const publicDir = join(root, 'public');
|
|
113
|
-
const filepath = join(publicDir, path.replace('public/', ''));
|
|
114
|
-
const file = Bun.file(filepath);
|
|
115
|
-
|
|
116
|
-
if (await file.exists()) {
|
|
117
|
-
return new Response(file);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
127
|
set.status = 404;
|
|
122
128
|
return 'File not found';
|
|
123
129
|
}
|
|
@@ -436,19 +442,6 @@ ws.onclose = () => {
|
|
|
436
442
|
});
|
|
437
443
|
})
|
|
438
444
|
|
|
439
|
-
.get('/public/*', async ({ params, set }) => {
|
|
440
|
-
const publicDir = join(root, 'public');
|
|
441
|
-
const filepath = join(publicDir, params['*']);
|
|
442
|
-
const file = Bun.file(filepath);
|
|
443
|
-
|
|
444
|
-
if (!await file.exists()) {
|
|
445
|
-
set.status = 404;
|
|
446
|
-
return 'File not found';
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return new Response(file);
|
|
450
|
-
})
|
|
451
|
-
|
|
452
445
|
.listen(port);
|
|
453
446
|
|
|
454
447
|
if (!app.server) {
|
|
@@ -458,7 +451,8 @@ ws.onclose = () => {
|
|
|
458
451
|
|
|
459
452
|
logger.success(`🚀 Server running at http://localhost:${port}`);
|
|
460
453
|
logger.info(`📁 Serving: ${root}`);
|
|
461
|
-
logger.info(`🖼️ Images
|
|
454
|
+
logger.info(`🖼️ Images: /images/* → src/images/`);
|
|
455
|
+
logger.info(`📦 Public: /public/* → public/`);
|
|
462
456
|
|
|
463
457
|
setupWatcher(root, compiledDir, clients, async () => {
|
|
464
458
|
hasRouter = existsSync(join(compiledDir, 'router.js'));
|
|
@@ -536,7 +530,6 @@ ${userStylesheets}
|
|
|
536
530
|
});
|
|
537
531
|
}
|
|
538
532
|
|
|
539
|
-
// ✅ NEW: Helper for image content types
|
|
540
533
|
function getImageContentType(ext) {
|
|
541
534
|
const types = {
|
|
542
535
|
'.jpg': 'image/jpeg',
|
|
@@ -594,14 +587,12 @@ function setupWatcher(root, compiledDir, clients, onRecompile) {
|
|
|
594
587
|
if (!filename) return;
|
|
595
588
|
|
|
596
589
|
const ext = extname(filename);
|
|
597
|
-
|
|
598
|
-
// ✅ Watch image changes too
|
|
599
590
|
const watchedExtensions = ['.js', '.jsx', '.ts', '.tsx', '.css', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif'];
|
|
600
591
|
|
|
601
592
|
if (watchedExtensions.includes(ext)) {
|
|
602
593
|
logger.info(`📝 File changed: ${filename}`);
|
|
603
594
|
|
|
604
|
-
// For images, just reload
|
|
595
|
+
// For images, just reload
|
|
605
596
|
if (['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif'].includes(ext)) {
|
|
606
597
|
for (const client of clients) {
|
|
607
598
|
try {
|