bertui 0.3.6 → 0.3.7
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.js +90 -79
- package/src/server/dev-server.js +12 -11
- package/src/utils/env.js +16 -5
package/package.json
CHANGED
package/src/build.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { join } from 'path';
|
|
1
|
+
import { join, relative, basename } from 'path';
|
|
2
2
|
import { existsSync, mkdirSync, rmSync, cpSync, readdirSync, statSync } from 'fs';
|
|
3
|
-
import { extname,
|
|
3
|
+
import { extname, dirname } from 'path';
|
|
4
4
|
import logger from './logger/logger.js';
|
|
5
5
|
import { buildCSS } from './build/css-builder.js';
|
|
6
6
|
|
|
@@ -448,41 +448,105 @@ function fixRelativeImports(code) {
|
|
|
448
448
|
return code;
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
-
//
|
|
451
|
+
// ✅ FIXED: Better meta extraction that handles multi-line objects
|
|
452
452
|
function extractMetaFromSource(code) {
|
|
453
453
|
try {
|
|
454
|
-
// Match
|
|
455
|
-
const
|
|
456
|
-
|
|
454
|
+
// Match export const meta = { ... } with proper brace counting
|
|
455
|
+
const metaMatch = code.match(/export\s+const\s+meta\s*=\s*\{/);
|
|
456
|
+
if (!metaMatch) return null;
|
|
457
|
+
|
|
458
|
+
const startIndex = metaMatch.index + metaMatch[0].length - 1; // Start at opening brace
|
|
459
|
+
let braceCount = 0;
|
|
460
|
+
let endIndex = startIndex;
|
|
461
|
+
|
|
462
|
+
// Count braces to find the closing brace
|
|
463
|
+
for (let i = startIndex; i < code.length; i++) {
|
|
464
|
+
if (code[i] === '{') braceCount++;
|
|
465
|
+
if (code[i] === '}') {
|
|
466
|
+
braceCount--;
|
|
467
|
+
if (braceCount === 0) {
|
|
468
|
+
endIndex = i;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
457
473
|
|
|
458
|
-
if (
|
|
474
|
+
if (endIndex === startIndex) return null;
|
|
459
475
|
|
|
460
|
-
const
|
|
461
|
-
const meta = {};
|
|
476
|
+
const metaString = code.substring(startIndex, endIndex + 1);
|
|
462
477
|
|
|
463
|
-
//
|
|
464
|
-
const
|
|
478
|
+
// Parse the meta object safely
|
|
479
|
+
const meta = {};
|
|
465
480
|
|
|
466
|
-
|
|
481
|
+
// Extract key-value pairs (handles strings with quotes)
|
|
482
|
+
const pairRegex = /(\w+)\s*:\s*(['"`])((?:(?!\2).)*)\2/g;
|
|
483
|
+
let match;
|
|
467
484
|
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
485
|
+
while ((match = pairRegex.exec(metaString)) !== null) {
|
|
486
|
+
const key = match[1];
|
|
487
|
+
const value = match[3];
|
|
488
|
+
meta[key] = value;
|
|
489
|
+
}
|
|
472
490
|
|
|
473
|
-
return meta;
|
|
491
|
+
return Object.keys(meta).length > 0 ? meta : null;
|
|
474
492
|
} catch (error) {
|
|
493
|
+
logger.warn(`Could not extract meta: ${error.message}`);
|
|
475
494
|
return null;
|
|
476
495
|
}
|
|
477
496
|
}
|
|
478
497
|
|
|
479
|
-
//
|
|
480
|
-
async function
|
|
481
|
-
|
|
482
|
-
|
|
498
|
+
// ✅ FIXED: Correct bundle path and proper hydration
|
|
499
|
+
async function generateProductionHTML(root, outDir, buildResult, routes) {
|
|
500
|
+
const mainBundle = buildResult.outputs.find(o =>
|
|
501
|
+
o.path.includes('main') && o.kind === 'entry-point'
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
if (!mainBundle) {
|
|
505
|
+
throw new Error('Could not find main bundle in build output');
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// ✅ FIX 1: Get ONLY the relative path from dist/
|
|
509
|
+
const bundlePath = relative(outDir, mainBundle.path).replace(/\\/g, '/');
|
|
510
|
+
|
|
511
|
+
logger.info(`Main bundle path: ${bundlePath}`);
|
|
512
|
+
|
|
513
|
+
const srcStylesDir = join(root, 'src', 'styles');
|
|
514
|
+
let userStylesheets = '';
|
|
483
515
|
|
|
484
|
-
|
|
485
|
-
|
|
516
|
+
if (existsSync(srcStylesDir)) {
|
|
517
|
+
const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
|
|
518
|
+
userStylesheets = cssFiles.map(f =>
|
|
519
|
+
` <link rel="stylesheet" href="/styles/${f.replace('.css', '.min.css')}">`
|
|
520
|
+
).join('\n');
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Load config for default meta
|
|
524
|
+
const { loadConfig } = await import('./config/loadConfig.js');
|
|
525
|
+
const config = await loadConfig(root);
|
|
526
|
+
const defaultMeta = config.meta || {};
|
|
527
|
+
|
|
528
|
+
logger.info('Generating SEO-optimized HTML files...');
|
|
529
|
+
|
|
530
|
+
for (const route of routes) {
|
|
531
|
+
if (route.type === 'dynamic') {
|
|
532
|
+
logger.info(`Skipping dynamic route: ${route.route}`);
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Read source file and extract meta
|
|
537
|
+
const sourceCode = await Bun.file(route.path).text();
|
|
538
|
+
const pageMeta = extractMetaFromSource(sourceCode);
|
|
539
|
+
|
|
540
|
+
// Merge page meta with default meta
|
|
541
|
+
const meta = { ...defaultMeta, ...pageMeta };
|
|
542
|
+
|
|
543
|
+
if (pageMeta) {
|
|
544
|
+
logger.info(`Extracted meta for ${route.route}: ${JSON.stringify(pageMeta)}`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ✅ FIX 2: Generate proper HTML with correct script path
|
|
548
|
+
const html = `<!DOCTYPE html>
|
|
549
|
+
<html lang="${meta.lang || 'en'}">
|
|
486
550
|
<head>
|
|
487
551
|
<meta charset="UTF-8">
|
|
488
552
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
@@ -497,7 +561,7 @@ async function renderComponentToHTML(route, bundlePath, userStylesheets, meta) {
|
|
|
497
561
|
<meta property="og:description" content="${meta.ogDescription || meta.description || 'Built with BertUI'}">
|
|
498
562
|
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
499
563
|
<meta property="og:type" content="website">
|
|
500
|
-
<meta property="og:url" content="${route}">
|
|
564
|
+
<meta property="og:url" content="${route.route}">
|
|
501
565
|
|
|
502
566
|
<meta name="twitter:card" content="summary_large_image">
|
|
503
567
|
<meta name="twitter:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
|
|
@@ -505,7 +569,7 @@ async function renderComponentToHTML(route, bundlePath, userStylesheets, meta) {
|
|
|
505
569
|
${meta.ogImage ? `<meta name="twitter:image" content="${meta.ogImage}">` : ''}
|
|
506
570
|
|
|
507
571
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
508
|
-
<link rel="canonical" href="${route}">
|
|
572
|
+
<link rel="canonical" href="${route.route}">
|
|
509
573
|
|
|
510
574
|
${userStylesheets}
|
|
511
575
|
|
|
@@ -519,65 +583,12 @@ ${userStylesheets}
|
|
|
519
583
|
}
|
|
520
584
|
}
|
|
521
585
|
</script>
|
|
522
|
-
|
|
523
|
-
<!-- SEO Preload Hints -->
|
|
524
|
-
<link rel="preconnect" href="https://esm.sh">
|
|
525
|
-
<link rel="dns-prefetch" href="https://esm.sh">
|
|
526
586
|
</head>
|
|
527
587
|
<body>
|
|
528
|
-
<div id="root">
|
|
529
|
-
<!-- App shell - JavaScript will hydrate this -->
|
|
530
|
-
<div style="display:flex;align-items:center;justify-content:center;min-height:100vh;font-family:system-ui">
|
|
531
|
-
<div style="text-align:center">
|
|
532
|
-
<h1 style="font-size:2rem;margin-bottom:1rem">${meta.title || 'Loading...'}</h1>
|
|
533
|
-
<p style="color:#666">${meta.description || 'Please wait while we load your content'}</p>
|
|
534
|
-
</div>
|
|
535
|
-
</div>
|
|
536
|
-
</div>
|
|
588
|
+
<div id="root"></div>
|
|
537
589
|
<script type="module" src="/${bundlePath}"></script>
|
|
538
590
|
</body>
|
|
539
591
|
</html>`;
|
|
540
|
-
|
|
541
|
-
return html;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
async function generateProductionHTML(root, outDir, buildResult, routes) {
|
|
545
|
-
const mainBundle = buildResult.outputs.find(o =>
|
|
546
|
-
o.path.includes('main') && o.kind === 'entry-point'
|
|
547
|
-
);
|
|
548
|
-
|
|
549
|
-
if (!mainBundle) {
|
|
550
|
-
throw new Error('Could not find main bundle in build output');
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const bundlePath = mainBundle.path.replace(outDir, '').replace(/^[\/\\]/, '');
|
|
554
|
-
|
|
555
|
-
const srcStylesDir = join(root, 'src', 'styles');
|
|
556
|
-
let userStylesheets = '';
|
|
557
|
-
|
|
558
|
-
if (existsSync(srcStylesDir)) {
|
|
559
|
-
const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
|
|
560
|
-
userStylesheets = cssFiles.map(f =>
|
|
561
|
-
` <link rel="stylesheet" href="/styles/${f.replace('.css', '.min.css')}">`
|
|
562
|
-
).join('\n');
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
logger.info('Generating SEO-optimized HTML files...');
|
|
566
|
-
|
|
567
|
-
for (const route of routes) {
|
|
568
|
-
if (route.type === 'dynamic') {
|
|
569
|
-
logger.info(`Skipping dynamic route: ${route.route}`);
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// Read source file and extract meta
|
|
574
|
-
const sourceCode = await Bun.file(route.path).text();
|
|
575
|
-
const meta = extractMetaFromSource(sourceCode) || {};
|
|
576
|
-
|
|
577
|
-
logger.info(`Extracting meta for ${route.route}: ${JSON.stringify(meta)}`);
|
|
578
|
-
|
|
579
|
-
// Generate HTML with meta tags and app shell
|
|
580
|
-
const html = await renderComponentToHTML(route.route, bundlePath, userStylesheets, meta);
|
|
581
592
|
|
|
582
593
|
// Determine output path
|
|
583
594
|
let htmlPath;
|
package/src/server/dev-server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
2
|
import { watch } from 'fs';
|
|
3
3
|
import { join, extname } from 'path';
|
|
4
|
-
import { existsSync } from 'fs';
|
|
4
|
+
import { existsSync, readdirSync } from 'fs'; // ✅ FIXED: Import properly
|
|
5
5
|
import logger from '../logger/logger.js';
|
|
6
6
|
import { compileProject } from '../client/compiler.js';
|
|
7
7
|
import { loadConfig } from '../config/loadConfig.js';
|
|
@@ -50,7 +50,7 @@ export async function startDevServer(options = {}) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
//
|
|
53
|
+
// Handle CSS files from .bertui/styles
|
|
54
54
|
if (path.startsWith('styles/') && path.endsWith('.css')) {
|
|
55
55
|
const cssPath = join(stylesDir, path.replace('styles/', ''));
|
|
56
56
|
const file = Bun.file(cssPath);
|
|
@@ -148,7 +148,6 @@ ws.onclose = () => {
|
|
|
148
148
|
});
|
|
149
149
|
})
|
|
150
150
|
|
|
151
|
-
// FIXED: Serve CSS from .bertui/styles
|
|
152
151
|
.get('/styles/*', async ({ params, set }) => {
|
|
153
152
|
const filepath = join(stylesDir, params['*']);
|
|
154
153
|
const file = Bun.file(filepath);
|
|
@@ -166,7 +165,6 @@ ws.onclose = () => {
|
|
|
166
165
|
});
|
|
167
166
|
})
|
|
168
167
|
|
|
169
|
-
// Around line 60, update the route handler:
|
|
170
168
|
.get('/public/*', async ({ params, set }) => {
|
|
171
169
|
const publicDir = join(root, 'public');
|
|
172
170
|
const filepath = join(publicDir, params['*']);
|
|
@@ -200,17 +198,20 @@ ws.onclose = () => {
|
|
|
200
198
|
function serveHTML(root, hasRouter, config) {
|
|
201
199
|
const meta = config.meta || {};
|
|
202
200
|
|
|
203
|
-
//
|
|
201
|
+
// ✅ FIXED: Proper ESM import for fs
|
|
204
202
|
const srcStylesDir = join(root, 'src', 'styles');
|
|
205
203
|
let userStylesheets = '';
|
|
206
204
|
|
|
207
205
|
if (existsSync(srcStylesDir)) {
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
try {
|
|
207
|
+
const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
|
|
208
|
+
userStylesheets = cssFiles.map(f => ` <link rel="stylesheet" href="/styles/${f}">`).join('\n');
|
|
209
|
+
} catch (error) {
|
|
210
|
+
logger.warn(`Could not read styles directory: ${error.message}`);
|
|
211
|
+
}
|
|
210
212
|
}
|
|
211
213
|
|
|
212
|
-
const html =
|
|
213
|
-
<!DOCTYPE html>
|
|
214
|
+
const html = `<!DOCTYPE html>
|
|
214
215
|
<html lang="${meta.lang || 'en'}">
|
|
215
216
|
<head>
|
|
216
217
|
<meta charset="UTF-8">
|
|
@@ -226,9 +227,9 @@ function serveHTML(root, hasRouter, config) {
|
|
|
226
227
|
${meta.ogDescription ? `<meta property="og:description" content="${meta.ogDescription || meta.description}">` : ''}
|
|
227
228
|
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
228
229
|
|
|
229
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
230
|
+
<link rel="icon" type="image/svg+xml" href="/public/favicon.svg">
|
|
230
231
|
|
|
231
|
-
|
|
232
|
+
${userStylesheets}
|
|
232
233
|
|
|
233
234
|
<script type="importmap">
|
|
234
235
|
{
|
package/src/utils/env.js
CHANGED
|
@@ -11,7 +11,7 @@ export function loadEnvVariables(root) {
|
|
|
11
11
|
|
|
12
12
|
// Load from process.env (already loaded by Bun/Node)
|
|
13
13
|
for (const [key, value] of Object.entries(process.env)) {
|
|
14
|
-
// Only expose variables that start with
|
|
14
|
+
// Only expose variables that start with BERTUI_ or PUBLIC_
|
|
15
15
|
if (key.startsWith('BERTUI_') || key.startsWith('PUBLIC_')) {
|
|
16
16
|
envVars[key] = value;
|
|
17
17
|
}
|
|
@@ -28,13 +28,12 @@ export function generateEnvCode(envVars) {
|
|
|
28
28
|
.map(([key, value]) => ` "${key}": ${JSON.stringify(value)}`)
|
|
29
29
|
.join(',\n');
|
|
30
30
|
|
|
31
|
-
return
|
|
32
|
-
// Environment variables injected at build time
|
|
31
|
+
return `// Environment variables injected at build time
|
|
33
32
|
export const env = {
|
|
34
33
|
${envObject}
|
|
35
34
|
};
|
|
36
35
|
|
|
37
|
-
Make it available globally (optional)
|
|
36
|
+
// Make it available globally (optional)
|
|
38
37
|
if (typeof window !== 'undefined') {
|
|
39
38
|
window.__BERTUI_ENV__ = env;
|
|
40
39
|
}
|
|
@@ -42,7 +41,8 @@ if (typeof window !== 'undefined') {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
|
-
* Replace process.env references
|
|
44
|
+
* ✅ CRITICAL FIX: Replace ALL process.env references with actual values
|
|
45
|
+
* This prevents "process is not defined" errors in the browser
|
|
46
46
|
*/
|
|
47
47
|
export function replaceEnvInCode(code, envVars) {
|
|
48
48
|
let result = code;
|
|
@@ -53,5 +53,16 @@ export function replaceEnvInCode(code, envVars) {
|
|
|
53
53
|
result = result.replace(regex, JSON.stringify(value));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// ✅ NEW: Also replace generic process.env.NODE_ENV
|
|
57
|
+
// This is commonly used by React and other libraries
|
|
58
|
+
result = result.replace(
|
|
59
|
+
/process\.env\.NODE_ENV/g,
|
|
60
|
+
JSON.stringify('production')
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// ✅ NEW: Remove any remaining process references that might cause errors
|
|
64
|
+
// Replace with undefined to avoid runtime errors
|
|
65
|
+
result = result.replace(/\bprocess\b/g, 'undefined');
|
|
66
|
+
|
|
56
67
|
return result;
|
|
57
68
|
}
|