bertui 0.3.3 → 0.3.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.js +78 -15
- package/src/client/compiler.js +38 -5
- package/src/server/dev-server.js +2 -1
- package/src/utils/env.js +57 -0
package/package.json
CHANGED
package/src/build.js
CHANGED
|
@@ -26,16 +26,22 @@ export async function buildProduction(options = {}) {
|
|
|
26
26
|
|
|
27
27
|
try {
|
|
28
28
|
logger.info('Step 1: Compiling for production...');
|
|
29
|
-
await compileForBuild(root, buildDir);
|
|
29
|
+
const { routes } = await compileForBuild(root, buildDir);
|
|
30
30
|
logger.success('Production compilation complete');
|
|
31
31
|
|
|
32
32
|
logger.info('Step 2: Building CSS with Lightning CSS...');
|
|
33
33
|
await buildAllCSS(root, outDir);
|
|
34
34
|
|
|
35
|
+
|
|
35
36
|
const publicDir = join(root, 'public');
|
|
36
37
|
if (existsSync(publicDir)) {
|
|
37
38
|
logger.info('Step 3: Copying public assets...');
|
|
38
|
-
|
|
39
|
+
const publicFiles = readdirSync(publicDir);
|
|
40
|
+
for (const file of publicFiles) {
|
|
41
|
+
const srcFile = join(publicDir, file);
|
|
42
|
+
const destFile = join(outDir, file);
|
|
43
|
+
cpSync(srcFile, destFile);
|
|
44
|
+
}
|
|
39
45
|
logger.success('Public assets copied');
|
|
40
46
|
} else {
|
|
41
47
|
logger.info('Step 3: No public directory found, skipping...');
|
|
@@ -72,8 +78,8 @@ export async function buildProduction(options = {}) {
|
|
|
72
78
|
|
|
73
79
|
logger.success('JavaScript bundled with tree-shaking');
|
|
74
80
|
|
|
75
|
-
logger.info('Step 5: Generating
|
|
76
|
-
await generateProductionHTML(root, outDir, result);
|
|
81
|
+
logger.info('Step 5: Generating HTML files with page-specific meta...');
|
|
82
|
+
await generateProductionHTML(root, outDir, result, routes);
|
|
77
83
|
|
|
78
84
|
rmSync(buildDir, { recursive: true });
|
|
79
85
|
logger.info('Cleaned up .bertuibuild/');
|
|
@@ -144,6 +150,8 @@ async function compileForBuild(root, buildDir) {
|
|
|
144
150
|
await generateBuildRouter(routes, buildDir);
|
|
145
151
|
logger.info('Generated router for build');
|
|
146
152
|
}
|
|
153
|
+
|
|
154
|
+
return { routes };
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
async function discoverRoutes(pagesDir) {
|
|
@@ -356,7 +364,6 @@ async function compileBuildDirectory(srcDir, buildDir, root) {
|
|
|
356
364
|
const outPath = join(buildDir, file);
|
|
357
365
|
let code = await Bun.file(srcPath).text();
|
|
358
366
|
|
|
359
|
-
// CRITICAL FIX: Remove CSS imports
|
|
360
367
|
code = removeCSSImports(code);
|
|
361
368
|
code = fixBuildImports(code, srcPath, outPath, root);
|
|
362
369
|
|
|
@@ -373,7 +380,6 @@ async function compileBuildFile(srcPath, buildDir, filename, root) {
|
|
|
373
380
|
try {
|
|
374
381
|
let code = await Bun.file(srcPath).text();
|
|
375
382
|
|
|
376
|
-
// CRITICAL FIX: Remove CSS imports before transpilation
|
|
377
383
|
code = removeCSSImports(code);
|
|
378
384
|
|
|
379
385
|
const outFilename = filename.replace(/\.(jsx|tsx|ts)$/, '.js');
|
|
@@ -407,7 +413,6 @@ async function compileBuildFile(srcPath, buildDir, filename, root) {
|
|
|
407
413
|
}
|
|
408
414
|
}
|
|
409
415
|
|
|
410
|
-
// NEW FUNCTION: Remove all CSS imports
|
|
411
416
|
function removeCSSImports(code) {
|
|
412
417
|
code = code.replace(/import\s+['"][^'"]*\.css['"];?\s*/g, '');
|
|
413
418
|
code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
|
|
@@ -442,7 +447,29 @@ function fixRelativeImports(code) {
|
|
|
442
447
|
return code;
|
|
443
448
|
}
|
|
444
449
|
|
|
445
|
-
|
|
450
|
+
// NEW: Extract meta from page component
|
|
451
|
+
async function extractMetaFromPage(pagePath) {
|
|
452
|
+
try {
|
|
453
|
+
const code = await Bun.file(pagePath).text();
|
|
454
|
+
|
|
455
|
+
// Match: export const meta = { ... }
|
|
456
|
+
const metaRegex = /export\s+const\s+meta\s*=\s*(\{[\s\S]*?\});/;
|
|
457
|
+
const match = code.match(metaRegex);
|
|
458
|
+
|
|
459
|
+
if (!match) return null;
|
|
460
|
+
|
|
461
|
+
// Parse the object (simple eval - production should use AST parser)
|
|
462
|
+
const metaStr = match[1];
|
|
463
|
+
const meta = eval(`(${metaStr})`);
|
|
464
|
+
|
|
465
|
+
return meta;
|
|
466
|
+
} catch (error) {
|
|
467
|
+
logger.warn(`Failed to extract meta from ${pagePath}: ${error.message}`);
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function generateProductionHTML(root, outDir, buildResult, routes) {
|
|
446
473
|
const mainBundle = buildResult.outputs.find(o =>
|
|
447
474
|
o.path.includes('main') && o.kind === 'entry-point'
|
|
448
475
|
);
|
|
@@ -453,7 +480,6 @@ async function generateProductionHTML(root, outDir, buildResult) {
|
|
|
453
480
|
|
|
454
481
|
const bundlePath = mainBundle.path.replace(outDir, '').replace(/^\//, '');
|
|
455
482
|
|
|
456
|
-
// Find user CSS files
|
|
457
483
|
const srcStylesDir = join(root, 'src', 'styles');
|
|
458
484
|
let userStylesheets = '';
|
|
459
485
|
|
|
@@ -464,14 +490,40 @@ async function generateProductionHTML(root, outDir, buildResult) {
|
|
|
464
490
|
).join('\n');
|
|
465
491
|
}
|
|
466
492
|
|
|
467
|
-
|
|
493
|
+
// Generate HTML for each route
|
|
494
|
+
logger.info('Generating HTML files for each route...');
|
|
495
|
+
|
|
496
|
+
for (const route of routes) {
|
|
497
|
+
if (route.type === 'dynamic') continue; // Skip dynamic routes
|
|
498
|
+
|
|
499
|
+
// Extract meta from page component
|
|
500
|
+
const pageMeta = await extractMetaFromPage(route.path);
|
|
501
|
+
|
|
502
|
+
const meta = pageMeta || {
|
|
503
|
+
title: 'BertUI App',
|
|
504
|
+
description: 'Built with BertUI - Lightning fast React development'
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const html = `<!DOCTYPE html>
|
|
468
508
|
<html lang="en">
|
|
469
509
|
<head>
|
|
470
510
|
<meta charset="UTF-8">
|
|
471
511
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
472
|
-
<meta
|
|
473
|
-
|
|
512
|
+
<title>${meta.title}</title>
|
|
513
|
+
|
|
514
|
+
<meta name="description" content="${meta.description || ''}">
|
|
515
|
+
${meta.keywords ? `<meta name="keywords" content="${meta.keywords}">` : ''}
|
|
516
|
+
${meta.author ? `<meta name="author" content="${meta.author}">` : ''}
|
|
517
|
+
${meta.themeColor ? `<meta name="theme-color" content="${meta.themeColor}">` : ''}
|
|
518
|
+
|
|
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}">` : ''}
|
|
521
|
+
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
522
|
+
|
|
523
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
524
|
+
|
|
474
525
|
${userStylesheets}
|
|
526
|
+
|
|
475
527
|
<script type="importmap">
|
|
476
528
|
{
|
|
477
529
|
"imports": {
|
|
@@ -488,7 +540,18 @@ ${userStylesheets}
|
|
|
488
540
|
<script type="module" src="/${bundlePath}"></script>
|
|
489
541
|
</body>
|
|
490
542
|
</html>`;
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
543
|
+
|
|
544
|
+
// Determine output path
|
|
545
|
+
let htmlPath;
|
|
546
|
+
if (route.route === '/') {
|
|
547
|
+
htmlPath = join(outDir, 'index.html');
|
|
548
|
+
} else {
|
|
549
|
+
const routeDir = join(outDir, route.route);
|
|
550
|
+
mkdirSync(routeDir, { recursive: true });
|
|
551
|
+
htmlPath = join(routeDir, 'index.html');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
await Bun.write(htmlPath, html);
|
|
555
|
+
logger.success(`Generated ${route.route === '/' ? 'index.html' : route.route + '/index.html'}`);
|
|
556
|
+
}
|
|
494
557
|
}
|
package/src/client/compiler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
2
2
|
import { join, extname, relative, dirname } from 'path';
|
|
3
3
|
import logger from '../logger/logger.js';
|
|
4
|
+
import { loadEnvVariables, generateEnvCode, replaceEnvInCode } from '../utils/env.js';
|
|
4
5
|
|
|
5
6
|
export async function compileProject(root) {
|
|
6
7
|
logger.bigLog('COMPILING PROJECT', { color: 'blue' });
|
|
@@ -19,6 +20,16 @@ export async function compileProject(root) {
|
|
|
19
20
|
logger.info('Created .bertui/compiled/');
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
// Load environment variables
|
|
24
|
+
const envVars = loadEnvVariables(root);
|
|
25
|
+
if (Object.keys(envVars).length > 0) {
|
|
26
|
+
logger.info(`Loaded ${Object.keys(envVars).length} environment variables`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Generate env.js file
|
|
30
|
+
const envCode = generateEnvCode(envVars);
|
|
31
|
+
await Bun.write(join(outDir, 'env.js'), envCode);
|
|
32
|
+
|
|
22
33
|
let routes = [];
|
|
23
34
|
if (existsSync(pagesDir)) {
|
|
24
35
|
routes = await discoverRoutes(pagesDir);
|
|
@@ -36,7 +47,7 @@ export async function compileProject(root) {
|
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
const startTime = Date.now();
|
|
39
|
-
const stats = await compileDirectory(srcDir, outDir, root);
|
|
50
|
+
const stats = await compileDirectory(srcDir, outDir, root, envVars);
|
|
40
51
|
const duration = Date.now() - startTime;
|
|
41
52
|
|
|
42
53
|
if (routes.length > 0) {
|
|
@@ -239,7 +250,7 @@ ${routeConfigs}
|
|
|
239
250
|
await Bun.write(routerPath, routerComponentCode);
|
|
240
251
|
}
|
|
241
252
|
|
|
242
|
-
async function compileDirectory(srcDir, outDir, root) {
|
|
253
|
+
async function compileDirectory(srcDir, outDir, root, envVars) {
|
|
243
254
|
const stats = { files: 0, skipped: 0 };
|
|
244
255
|
|
|
245
256
|
const files = readdirSync(srcDir);
|
|
@@ -251,7 +262,7 @@ async function compileDirectory(srcDir, outDir, root) {
|
|
|
251
262
|
if (stat.isDirectory()) {
|
|
252
263
|
const subOutDir = join(outDir, file);
|
|
253
264
|
mkdirSync(subOutDir, { recursive: true });
|
|
254
|
-
const subStats = await compileDirectory(srcPath, subOutDir, root);
|
|
265
|
+
const subStats = await compileDirectory(srcPath, subOutDir, root, envVars);
|
|
255
266
|
stats.files += subStats.files;
|
|
256
267
|
stats.skipped += subStats.skipped;
|
|
257
268
|
} else {
|
|
@@ -268,7 +279,7 @@ async function compileDirectory(srcDir, outDir, root) {
|
|
|
268
279
|
logger.debug(`Copied CSS: ${relativePath}`);
|
|
269
280
|
stats.files++;
|
|
270
281
|
} else if (['.jsx', '.tsx', '.ts'].includes(ext)) {
|
|
271
|
-
await compileFile(srcPath, outDir, file, relativePath, root);
|
|
282
|
+
await compileFile(srcPath, outDir, file, relativePath, root, envVars);
|
|
272
283
|
stats.files++;
|
|
273
284
|
} else if (ext === '.js') {
|
|
274
285
|
const outPath = join(outDir, file);
|
|
@@ -276,6 +287,8 @@ async function compileDirectory(srcDir, outDir, root) {
|
|
|
276
287
|
|
|
277
288
|
// Remove ALL CSS imports
|
|
278
289
|
code = removeCSSImports(code);
|
|
290
|
+
// Inject environment variables
|
|
291
|
+
code = replaceEnvInCode(code, envVars);
|
|
279
292
|
// Fix router imports
|
|
280
293
|
code = fixRouterImports(code, outPath, root);
|
|
281
294
|
|
|
@@ -292,7 +305,7 @@ async function compileDirectory(srcDir, outDir, root) {
|
|
|
292
305
|
return stats;
|
|
293
306
|
}
|
|
294
307
|
|
|
295
|
-
async function compileFile(srcPath, outDir, filename, relativePath, root) {
|
|
308
|
+
async function compileFile(srcPath, outDir, filename, relativePath, root, envVars) {
|
|
296
309
|
const ext = extname(filename);
|
|
297
310
|
const loader = ext === '.tsx' ? 'tsx' : ext === '.ts' ? 'ts' : 'jsx';
|
|
298
311
|
|
|
@@ -302,6 +315,12 @@ async function compileFile(srcPath, outDir, filename, relativePath, root) {
|
|
|
302
315
|
// CRITICAL FIX: Remove ALL CSS imports before transpilation
|
|
303
316
|
code = removeCSSImports(code);
|
|
304
317
|
|
|
318
|
+
// Remove dotenv imports (not needed in browser)
|
|
319
|
+
code = removeDotenvImports(code);
|
|
320
|
+
|
|
321
|
+
// Inject environment variables
|
|
322
|
+
code = replaceEnvInCode(code, envVars);
|
|
323
|
+
|
|
305
324
|
const outPath = join(outDir, filename.replace(/\.(jsx|tsx|ts)$/, '.js'));
|
|
306
325
|
code = fixRouterImports(code, outPath, root);
|
|
307
326
|
|
|
@@ -343,6 +362,20 @@ function removeCSSImports(code) {
|
|
|
343
362
|
return code;
|
|
344
363
|
}
|
|
345
364
|
|
|
365
|
+
// NEW FUNCTION: Remove dotenv imports and dotenv.config() calls
|
|
366
|
+
function removeDotenvImports(code) {
|
|
367
|
+
// Remove: import dotenv from 'dotenv'
|
|
368
|
+
code = code.replace(/import\s+\w+\s+from\s+['"]dotenv['"]\s*;?\s*/g, '');
|
|
369
|
+
|
|
370
|
+
// Remove: import { config } from 'dotenv'
|
|
371
|
+
code = code.replace(/import\s+\{[^}]+\}\s+from\s+['"]dotenv['"]\s*;?\s*/g, '');
|
|
372
|
+
|
|
373
|
+
// Remove: dotenv.config()
|
|
374
|
+
code = code.replace(/\w+\.config\(\s*\)\s*;?\s*/g, '');
|
|
375
|
+
|
|
376
|
+
return code;
|
|
377
|
+
}
|
|
378
|
+
|
|
346
379
|
function fixRouterImports(code, outPath, root) {
|
|
347
380
|
const buildDir = join(root, '.bertui', 'compiled');
|
|
348
381
|
const routerPath = join(buildDir, 'router.js');
|
package/src/server/dev-server.js
CHANGED
|
@@ -166,6 +166,7 @@ ws.onclose = () => {
|
|
|
166
166
|
});
|
|
167
167
|
})
|
|
168
168
|
|
|
169
|
+
// Around line 60, update the route handler:
|
|
169
170
|
.get('/public/*', async ({ params, set }) => {
|
|
170
171
|
const publicDir = join(root, 'public');
|
|
171
172
|
const filepath = join(publicDir, params['*']);
|
|
@@ -225,7 +226,7 @@ function serveHTML(root, hasRouter, config) {
|
|
|
225
226
|
${meta.ogDescription ? `<meta property="og:description" content="${meta.ogDescription || meta.description}">` : ''}
|
|
226
227
|
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
227
228
|
|
|
228
|
-
<link rel="icon" type="image/svg+xml" href="/
|
|
229
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
229
230
|
|
|
230
231
|
${userStylesheets}
|
|
231
232
|
|
package/src/utils/env.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// bertui/src/utils/env.js
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Load environment variables for BertUI
|
|
7
|
+
* This runs at BUILD TIME (Node.js), not in the browser
|
|
8
|
+
*/
|
|
9
|
+
export function loadEnvVariables(root) {
|
|
10
|
+
const envVars = {};
|
|
11
|
+
|
|
12
|
+
// Load from process.env (already loaded by Bun/Node)
|
|
13
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
14
|
+
// Only expose variables that start with VITE_ or PUBLIC_
|
|
15
|
+
if (key.startsWith('BERTUI_') || key.startsWith('PUBLIC_')) {
|
|
16
|
+
envVars[key] = value;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return envVars;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate code to inject env variables into the browser
|
|
25
|
+
*/
|
|
26
|
+
export function generateEnvCode(envVars) {
|
|
27
|
+
const envObject = Object.entries(envVars)
|
|
28
|
+
.map(([key, value]) => ` "${key}": ${JSON.stringify(value)}`)
|
|
29
|
+
.join(',\n');
|
|
30
|
+
|
|
31
|
+
return `
|
|
32
|
+
// Environment variables injected at build time
|
|
33
|
+
export const env = {
|
|
34
|
+
${envObject}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
Make it available globally (optional)
|
|
38
|
+
if (typeof window !== 'undefined') {
|
|
39
|
+
window.__BERTUI_ENV__ = env;
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Replace process.env references in code with actual values
|
|
46
|
+
*/
|
|
47
|
+
export function replaceEnvInCode(code, envVars) {
|
|
48
|
+
let result = code;
|
|
49
|
+
|
|
50
|
+
// Replace process.env.VARIABLE_NAME with actual values
|
|
51
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
52
|
+
const regex = new RegExp(`process\\.env\\.${key}`, 'g');
|
|
53
|
+
result = result.replace(regex, JSON.stringify(value));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
}
|