bertui 0.3.5 → 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 +83 -34
- 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
|
|
|
@@ -32,15 +32,16 @@ export async function buildProduction(options = {}) {
|
|
|
32
32
|
logger.info('Step 2: Building CSS with Lightning CSS...');
|
|
33
33
|
await buildAllCSS(root, outDir);
|
|
34
34
|
|
|
35
|
-
|
|
36
35
|
const publicDir = join(root, 'public');
|
|
37
36
|
if (existsSync(publicDir)) {
|
|
38
37
|
logger.info('Step 3: Copying public assets...');
|
|
39
38
|
const publicFiles = readdirSync(publicDir);
|
|
40
39
|
for (const file of publicFiles) {
|
|
41
40
|
const srcFile = join(publicDir, file);
|
|
42
|
-
const destFile = join(outDir, file);
|
|
43
|
-
|
|
41
|
+
const destFile = join(outDir, file);
|
|
42
|
+
if (statSync(srcFile).isFile()) {
|
|
43
|
+
cpSync(srcFile, destFile);
|
|
44
|
+
}
|
|
44
45
|
}
|
|
45
46
|
logger.success('Public assets copied');
|
|
46
47
|
} else {
|
|
@@ -78,7 +79,7 @@ export async function buildProduction(options = {}) {
|
|
|
78
79
|
|
|
79
80
|
logger.success('JavaScript bundled with tree-shaking');
|
|
80
81
|
|
|
81
|
-
logger.info('Step 5: Generating HTML files
|
|
82
|
+
logger.info('Step 5: Generating SEO-optimized HTML files...');
|
|
82
83
|
await generateProductionHTML(root, outDir, result, routes);
|
|
83
84
|
|
|
84
85
|
rmSync(buildDir, { recursive: true });
|
|
@@ -447,28 +448,54 @@ function fixRelativeImports(code) {
|
|
|
447
448
|
return code;
|
|
448
449
|
}
|
|
449
450
|
|
|
450
|
-
//
|
|
451
|
-
|
|
451
|
+
// ✅ FIXED: Better meta extraction that handles multi-line objects
|
|
452
|
+
function extractMetaFromSource(code) {
|
|
452
453
|
try {
|
|
453
|
-
const
|
|
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
|
+
}
|
|
473
|
+
|
|
474
|
+
if (endIndex === startIndex) return null;
|
|
454
475
|
|
|
455
|
-
|
|
456
|
-
const metaRegex = /export\s+const\s+meta\s*=\s*(\{[\s\S]*?\});/;
|
|
457
|
-
const match = code.match(metaRegex);
|
|
476
|
+
const metaString = code.substring(startIndex, endIndex + 1);
|
|
458
477
|
|
|
459
|
-
|
|
478
|
+
// Parse the meta object safely
|
|
479
|
+
const meta = {};
|
|
460
480
|
|
|
461
|
-
//
|
|
462
|
-
const
|
|
463
|
-
|
|
481
|
+
// Extract key-value pairs (handles strings with quotes)
|
|
482
|
+
const pairRegex = /(\w+)\s*:\s*(['"`])((?:(?!\2).)*)\2/g;
|
|
483
|
+
let match;
|
|
484
|
+
|
|
485
|
+
while ((match = pairRegex.exec(metaString)) !== null) {
|
|
486
|
+
const key = match[1];
|
|
487
|
+
const value = match[3];
|
|
488
|
+
meta[key] = value;
|
|
489
|
+
}
|
|
464
490
|
|
|
465
|
-
return meta;
|
|
491
|
+
return Object.keys(meta).length > 0 ? meta : null;
|
|
466
492
|
} catch (error) {
|
|
467
|
-
logger.warn(`
|
|
493
|
+
logger.warn(`Could not extract meta: ${error.message}`);
|
|
468
494
|
return null;
|
|
469
495
|
}
|
|
470
496
|
}
|
|
471
497
|
|
|
498
|
+
// ✅ FIXED: Correct bundle path and proper hydration
|
|
472
499
|
async function generateProductionHTML(root, outDir, buildResult, routes) {
|
|
473
500
|
const mainBundle = buildResult.outputs.find(o =>
|
|
474
501
|
o.path.includes('main') && o.kind === 'entry-point'
|
|
@@ -478,7 +505,10 @@ async function generateProductionHTML(root, outDir, buildResult, routes) {
|
|
|
478
505
|
throw new Error('Could not find main bundle in build output');
|
|
479
506
|
}
|
|
480
507
|
|
|
481
|
-
|
|
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}`);
|
|
482
512
|
|
|
483
513
|
const srcStylesDir = join(root, 'src', 'styles');
|
|
484
514
|
let userStylesheets = '';
|
|
@@ -490,37 +520,56 @@ async function generateProductionHTML(root, outDir, buildResult, routes) {
|
|
|
490
520
|
).join('\n');
|
|
491
521
|
}
|
|
492
522
|
|
|
493
|
-
//
|
|
494
|
-
|
|
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...');
|
|
495
529
|
|
|
496
530
|
for (const route of routes) {
|
|
497
|
-
if (route.type === 'dynamic')
|
|
531
|
+
if (route.type === 'dynamic') {
|
|
532
|
+
logger.info(`Skipping dynamic route: ${route.route}`);
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
498
535
|
|
|
499
|
-
//
|
|
500
|
-
const
|
|
536
|
+
// Read source file and extract meta
|
|
537
|
+
const sourceCode = await Bun.file(route.path).text();
|
|
538
|
+
const pageMeta = extractMetaFromSource(sourceCode);
|
|
501
539
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
description: 'Built with BertUI - Lightning fast React development'
|
|
505
|
-
};
|
|
540
|
+
// Merge page meta with default meta
|
|
541
|
+
const meta = { ...defaultMeta, ...pageMeta };
|
|
506
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
|
|
507
548
|
const html = `<!DOCTYPE html>
|
|
508
|
-
<html lang="en">
|
|
549
|
+
<html lang="${meta.lang || 'en'}">
|
|
509
550
|
<head>
|
|
510
551
|
<meta charset="UTF-8">
|
|
511
552
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
512
|
-
<title>${meta.title}</title>
|
|
553
|
+
<title>${meta.title || 'BertUI App'}</title>
|
|
513
554
|
|
|
514
|
-
<meta name="description" content="${meta.description || ''}">
|
|
555
|
+
<meta name="description" content="${meta.description || 'Built with BertUI - Lightning fast React development'}">
|
|
515
556
|
${meta.keywords ? `<meta name="keywords" content="${meta.keywords}">` : ''}
|
|
516
557
|
${meta.author ? `<meta name="author" content="${meta.author}">` : ''}
|
|
517
558
|
${meta.themeColor ? `<meta name="theme-color" content="${meta.themeColor}">` : ''}
|
|
518
559
|
|
|
519
|
-
|
|
520
|
-
|
|
560
|
+
<meta property="og:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
|
|
561
|
+
<meta property="og:description" content="${meta.ogDescription || meta.description || 'Built with BertUI'}">
|
|
521
562
|
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
563
|
+
<meta property="og:type" content="website">
|
|
564
|
+
<meta property="og:url" content="${route.route}">
|
|
565
|
+
|
|
566
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
567
|
+
<meta name="twitter:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
|
|
568
|
+
<meta name="twitter:description" content="${meta.ogDescription || meta.description || 'Built with BertUI'}">
|
|
569
|
+
${meta.ogImage ? `<meta name="twitter:image" content="${meta.ogImage}">` : ''}
|
|
522
570
|
|
|
523
571
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
572
|
+
<link rel="canonical" href="${route.route}">
|
|
524
573
|
|
|
525
574
|
${userStylesheets}
|
|
526
575
|
|
|
@@ -552,6 +601,6 @@ ${userStylesheets}
|
|
|
552
601
|
}
|
|
553
602
|
|
|
554
603
|
await Bun.write(htmlPath, html);
|
|
555
|
-
logger.success(`Generated ${route.route === '/' ? 'index.html' : route.route + '/index.html'}`);
|
|
604
|
+
logger.success(`Generated ${route.route === '/' ? 'index.html' : route.route + '/index.html'} with meta: ${meta.title || 'default'}`);
|
|
556
605
|
}
|
|
557
606
|
}
|
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
|
}
|