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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bertui",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Lightning-fast React dev server powered by Bun and Elysia",
5
5
  "type": "module",
6
6
  "main": "./index.js",
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, relative, dirname } from 'path';
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
- cpSync(srcFile, destFile);
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 with page-specific meta...');
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
- // NEW: Extract meta from page component
451
- async function extractMetaFromPage(pagePath) {
451
+ // ✅ FIXED: Better meta extraction that handles multi-line objects
452
+ function extractMetaFromSource(code) {
452
453
  try {
453
- const code = await Bun.file(pagePath).text();
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
- // Match: export const meta = { ... }
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
- if (!match) return null;
478
+ // Parse the meta object safely
479
+ const meta = {};
460
480
 
461
- // Parse the object (simple eval - production should use AST parser)
462
- const metaStr = match[1];
463
- const meta = eval(`(${metaStr})`);
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(`Failed to extract meta from ${pagePath}: ${error.message}`);
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
- const bundlePath = mainBundle.path.replace(outDir, '').replace(/^\//, '');
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
- // Generate HTML for each route
494
- logger.info('Generating HTML files for each route...');
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') continue; // Skip dynamic routes
531
+ if (route.type === 'dynamic') {
532
+ logger.info(`Skipping dynamic route: ${route.route}`);
533
+ continue;
534
+ }
498
535
 
499
- // Extract meta from page component
500
- const pageMeta = await extractMetaFromPage(route.path);
536
+ // Read source file and extract meta
537
+ const sourceCode = await Bun.file(route.path).text();
538
+ const pageMeta = extractMetaFromSource(sourceCode);
501
539
 
502
- const meta = pageMeta || {
503
- title: 'BertUI App',
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
- ${meta.ogTitle ? `<meta property="og:title" content="${meta.ogTitle}">` : `<meta property="og:title" content="${meta.title}">`}
520
- ${meta.ogDescription ? `<meta property="og:description" content="${meta.ogDescription}">` : meta.description ? `<meta property="og:description" content="${meta.description}">` : ''}
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
  }
@@ -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
- // FIXED: Handle CSS files from .bertui/styles
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
- // Find user's CSS files
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
- const cssFiles = require('fs').readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
209
- userStylesheets = cssFiles.map(f => ` <link rel="stylesheet" href="/styles/${f}">`).join('\n');
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
- ${userStylesheets}
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 VITE_ or PUBLIC_
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 in code with actual values
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
  }