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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bertui",
3
- "version": "0.3.6",
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
 
@@ -448,41 +448,105 @@ function fixRelativeImports(code) {
448
448
  return code;
449
449
  }
450
450
 
451
- // IMPROVED: Extract meta using regex (works on raw source code)
451
+ // ✅ FIXED: Better meta extraction that handles multi-line objects
452
452
  function extractMetaFromSource(code) {
453
453
  try {
454
- // Match: export const meta = { ... };
455
- const metaRegex = /export\s+const\s+meta\s*=\s*\{([^}]+)\}/s;
456
- const match = code.match(metaRegex);
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 (!match) return null;
474
+ if (endIndex === startIndex) return null;
459
475
 
460
- const metaContent = match[1];
461
- const meta = {};
476
+ const metaString = code.substring(startIndex, endIndex + 1);
462
477
 
463
- // Extract key-value pairs
464
- const pairs = metaContent.match(/(\w+)\s*:\s*(['"`])((?:(?!\2).)*)\2/g);
478
+ // Parse the meta object safely
479
+ const meta = {};
465
480
 
466
- if (!pairs) return null;
481
+ // Extract key-value pairs (handles strings with quotes)
482
+ const pairRegex = /(\w+)\s*:\s*(['"`])((?:(?!\2).)*)\2/g;
483
+ let match;
467
484
 
468
- pairs.forEach(pair => {
469
- const [key, value] = pair.split(':').map(s => s.trim());
470
- meta[key] = value.replace(/['"]/g, '');
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
- // IMPROVED: Render component to static HTML using JSDOM-like approach
480
- async function renderComponentToHTML(route, bundlePath, userStylesheets, meta) {
481
- // For now, we'll create a basic shell with meta tags
482
- // Full SSR would require running React server-side
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
- const html = `<!DOCTYPE html>
485
- <html lang="en">
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;
@@ -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
  }