bertui 0.3.2 → 0.3.4

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.2",
3
+ "version": "0.3.4",
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
@@ -11,7 +11,6 @@ export async function buildProduction(options = {}) {
11
11
 
12
12
  logger.bigLog('BUILDING FOR PRODUCTION', { color: 'green' });
13
13
 
14
- // Clean folders
15
14
  if (existsSync(buildDir)) {
16
15
  rmSync(buildDir, { recursive: true });
17
16
  }
@@ -26,16 +25,13 @@ export async function buildProduction(options = {}) {
26
25
  const startTime = Date.now();
27
26
 
28
27
  try {
29
- // Step 1: Compile for production
30
28
  logger.info('Step 1: Compiling for production...');
31
29
  await compileForBuild(root, buildDir);
32
30
  logger.success('Production compilation complete');
33
31
 
34
- // Step 2: Build CSS with Lightning CSS
35
32
  logger.info('Step 2: Building CSS with Lightning CSS...');
36
33
  await buildAllCSS(root, outDir);
37
34
 
38
- // Step 3: Copy public assets if they exist
39
35
  const publicDir = join(root, 'public');
40
36
  if (existsSync(publicDir)) {
41
37
  logger.info('Step 3: Copying public assets...');
@@ -45,7 +41,6 @@ export async function buildProduction(options = {}) {
45
41
  logger.info('Step 3: No public directory found, skipping...');
46
42
  }
47
43
 
48
- // Step 4: Build JavaScript with Bun's bundler
49
44
  logger.info('Step 4: Bundling JavaScript with Bun...');
50
45
  const buildEntry = join(buildDir, 'main.js');
51
46
 
@@ -54,7 +49,6 @@ export async function buildProduction(options = {}) {
54
49
  process.exit(1);
55
50
  }
56
51
 
57
- // FIXED: Let Bun handle ALL imports with proper externals
58
52
  const result = await Bun.build({
59
53
  entrypoints: [buildEntry],
60
54
  outdir: join(outDir, 'assets'),
@@ -67,7 +61,6 @@ export async function buildProduction(options = {}) {
67
61
  chunk: 'chunks/[name]-[hash].js',
68
62
  asset: '[name]-[hash].[ext]'
69
63
  },
70
- // FIXED: Use CDN externals - Bun handles tree shaking automatically
71
64
  external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime']
72
65
  });
73
66
 
@@ -79,11 +72,9 @@ export async function buildProduction(options = {}) {
79
72
 
80
73
  logger.success('JavaScript bundled with tree-shaking');
81
74
 
82
- // Step 5: Generate index.html
83
75
  logger.info('Step 5: Generating index.html...');
84
76
  await generateProductionHTML(root, outDir, result);
85
77
 
86
- // Step 6: Clean up build folder
87
78
  rmSync(buildDir, { recursive: true });
88
79
  logger.info('Cleaned up .bertuibuild/');
89
80
 
@@ -91,13 +82,11 @@ export async function buildProduction(options = {}) {
91
82
  logger.success(`✨ Build complete in ${duration}ms`);
92
83
  logger.info(`šŸ“¦ Output: ${outDir}`);
93
84
 
94
- // Display build stats
95
85
  logger.table(result.outputs.map(o => ({
96
86
  file: o.path.replace(outDir, ''),
97
87
  size: `${(o.size / 1024).toFixed(2)} KB`
98
88
  })));
99
89
 
100
- // Show deployment instructions
101
90
  logger.bigLog('READY TO DEPLOY', { color: 'green' });
102
91
  console.log('\nšŸ“¤ Deploy your app:\n');
103
92
  console.log(' Vercel: bunx vercel');
@@ -111,7 +100,6 @@ export async function buildProduction(options = {}) {
111
100
  logger.error(error.stack);
112
101
  }
113
102
 
114
- // Clean up on error
115
103
  if (existsSync(buildDir)) {
116
104
  rmSync(buildDir, { recursive: true });
117
105
  }
@@ -122,15 +110,10 @@ export async function buildProduction(options = {}) {
122
110
 
123
111
  async function buildAllCSS(root, outDir) {
124
112
  const srcStylesDir = join(root, 'src', 'styles');
125
- const bertuiCssSource = join(import.meta.dir, 'styles/bertui.css');
126
113
  const stylesOutDir = join(outDir, 'styles');
127
114
 
128
115
  mkdirSync(stylesOutDir, { recursive: true });
129
116
 
130
- // Build BertUI's built-in CSS
131
- await buildCSS(bertuiCssSource, join(stylesOutDir, 'bertui.min.css'));
132
-
133
- // Build user's CSS files if they exist
134
117
  if (existsSync(srcStylesDir)) {
135
118
  const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
136
119
  for (const cssFile of cssFiles) {
@@ -149,17 +132,14 @@ async function compileForBuild(root, buildDir) {
149
132
  throw new Error('src/ directory not found!');
150
133
  }
151
134
 
152
- // Discover routes
153
135
  let routes = [];
154
136
  if (existsSync(pagesDir)) {
155
137
  routes = await discoverRoutes(pagesDir);
156
138
  logger.info(`Found ${routes.length} routes`);
157
139
  }
158
140
 
159
- // Compile all source files
160
141
  await compileBuildDirectory(srcDir, buildDir, root);
161
142
 
162
- // Generate router if we have routes
163
143
  if (routes.length > 0) {
164
144
  await generateBuildRouter(routes, buildDir);
165
145
  logger.info('Generated router for build');
@@ -181,7 +161,6 @@ async function discoverRoutes(pagesDir) {
181
161
  } else if (entry.isFile()) {
182
162
  const ext = extname(entry.name);
183
163
 
184
- // FIXED: Ignore CSS files
185
164
  if (ext === '.css') continue;
186
165
 
187
166
  if (['.jsx', '.tsx', '.js', '.ts'].includes(ext)) {
@@ -369,7 +348,6 @@ async function compileBuildDirectory(srcDir, buildDir, root) {
369
348
  } else {
370
349
  const ext = extname(file);
371
350
 
372
- // FIXED: Skip CSS files in build
373
351
  if (ext === '.css') continue;
374
352
 
375
353
  if (['.jsx', '.tsx', '.ts'].includes(ext)) {
@@ -377,7 +355,11 @@ async function compileBuildDirectory(srcDir, buildDir, root) {
377
355
  } else if (ext === '.js') {
378
356
  const outPath = join(buildDir, file);
379
357
  let code = await Bun.file(srcPath).text();
358
+
359
+ // CRITICAL FIX: Remove CSS imports
360
+ code = removeCSSImports(code);
380
361
  code = fixBuildImports(code, srcPath, outPath, root);
362
+
381
363
  await Bun.write(outPath, code);
382
364
  }
383
365
  }
@@ -391,6 +373,9 @@ async function compileBuildFile(srcPath, buildDir, filename, root) {
391
373
  try {
392
374
  let code = await Bun.file(srcPath).text();
393
375
 
376
+ // CRITICAL FIX: Remove CSS imports before transpilation
377
+ code = removeCSSImports(code);
378
+
394
379
  const outFilename = filename.replace(/\.(jsx|tsx|ts)$/, '.js');
395
380
  const outPath = join(buildDir, outFilename);
396
381
 
@@ -422,19 +407,20 @@ async function compileBuildFile(srcPath, buildDir, filename, root) {
422
407
  }
423
408
  }
424
409
 
425
- // FIXED: Only fix router imports, preserve all others
426
- function fixBuildImports(code, srcPath, outPath, root) {
427
- // Remove bertui/styles imports
410
+ // NEW FUNCTION: Remove all CSS imports
411
+ function removeCSSImports(code) {
412
+ code = code.replace(/import\s+['"][^'"]*\.css['"];?\s*/g, '');
428
413
  code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
429
-
414
+ return code;
415
+ }
416
+
417
+ function fixBuildImports(code, srcPath, outPath, root) {
430
418
  const buildDir = join(root, '.bertuibuild');
431
419
  const routerPath = join(buildDir, 'router.js');
432
420
 
433
- // Calculate relative path from output file to router.js
434
421
  const relativeToRouter = relative(dirname(outPath), routerPath).replace(/\\/g, '/');
435
422
  const routerImport = relativeToRouter.startsWith('.') ? relativeToRouter : './' + relativeToRouter;
436
423
 
437
- // ONLY replace bertui/router imports
438
424
  code = code.replace(
439
425
  /from\s+['"]bertui\/router['"]/g,
440
426
  `from '${routerImport}'`
@@ -467,6 +453,17 @@ async function generateProductionHTML(root, outDir, buildResult) {
467
453
 
468
454
  const bundlePath = mainBundle.path.replace(outDir, '').replace(/^\//, '');
469
455
 
456
+ // Find user CSS files
457
+ const srcStylesDir = join(root, 'src', 'styles');
458
+ let userStylesheets = '';
459
+
460
+ if (existsSync(srcStylesDir)) {
461
+ const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
462
+ userStylesheets = cssFiles.map(f =>
463
+ ` <link rel="stylesheet" href="/styles/${f.replace('.css', '.min.css')}">`
464
+ ).join('\n');
465
+ }
466
+
470
467
  const html = `<!DOCTYPE html>
471
468
  <html lang="en">
472
469
  <head>
@@ -474,7 +471,7 @@ async function generateProductionHTML(root, outDir, buildResult) {
474
471
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
475
472
  <meta name="description" content="Built with BertUI - Lightning fast React development">
476
473
  <title>BertUI App</title>
477
- <link rel="stylesheet" href="/styles/bertui.min.css">
474
+ ${userStylesheets}
478
475
  <script type="importmap">
479
476
  {
480
477
  "imports": {
@@ -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) {
@@ -65,13 +76,8 @@ async function discoverRoutes(pagesDir) {
65
76
  } else if (entry.isFile()) {
66
77
  const ext = extname(entry.name);
67
78
 
68
- // FIXED: Ignore CSS files completely
69
- if (ext === '.css') {
70
- logger.debug(`Skipping CSS file: ${relativePath}`);
71
- continue;
72
- }
79
+ if (ext === '.css') continue;
73
80
 
74
- // Only process valid page files
75
81
  if (['.jsx', '.tsx', '.js', '.ts'].includes(ext)) {
76
82
  const fileName = entry.name.replace(ext, '');
77
83
 
@@ -244,7 +250,7 @@ ${routeConfigs}
244
250
  await Bun.write(routerPath, routerComponentCode);
245
251
  }
246
252
 
247
- async function compileDirectory(srcDir, outDir, root) {
253
+ async function compileDirectory(srcDir, outDir, root, envVars) {
248
254
  const stats = { files: 0, skipped: 0 };
249
255
 
250
256
  const files = readdirSync(srcDir);
@@ -256,14 +262,13 @@ async function compileDirectory(srcDir, outDir, root) {
256
262
  if (stat.isDirectory()) {
257
263
  const subOutDir = join(outDir, file);
258
264
  mkdirSync(subOutDir, { recursive: true });
259
- const subStats = await compileDirectory(srcPath, subOutDir, root);
265
+ const subStats = await compileDirectory(srcPath, subOutDir, root, envVars);
260
266
  stats.files += subStats.files;
261
267
  stats.skipped += subStats.skipped;
262
268
  } else {
263
269
  const ext = extname(file);
264
270
  const relativePath = relative(join(root, 'src'), srcPath);
265
271
 
266
- // FIXED: Handle CSS files properly - copy to styles output
267
272
  if (ext === '.css') {
268
273
  const stylesOutDir = join(root, '.bertui', 'styles');
269
274
  if (!existsSync(stylesOutDir)) {
@@ -274,14 +279,17 @@ async function compileDirectory(srcDir, outDir, root) {
274
279
  logger.debug(`Copied CSS: ${relativePath}`);
275
280
  stats.files++;
276
281
  } else if (['.jsx', '.tsx', '.ts'].includes(ext)) {
277
- await compileFile(srcPath, outDir, file, relativePath);
282
+ await compileFile(srcPath, outDir, file, relativePath, root, envVars);
278
283
  stats.files++;
279
284
  } else if (ext === '.js') {
280
285
  const outPath = join(outDir, file);
281
286
  let code = await Bun.file(srcPath).text();
282
287
 
283
- // FIXED: Don't modify imports - let Bun handle them
284
- // Only fix router imports
288
+ // Remove ALL CSS imports
289
+ code = removeCSSImports(code);
290
+ // Inject environment variables
291
+ code = replaceEnvInCode(code, envVars);
292
+ // Fix router imports
285
293
  code = fixRouterImports(code, outPath, root);
286
294
 
287
295
  await Bun.write(outPath, code);
@@ -297,17 +305,24 @@ async function compileDirectory(srcDir, outDir, root) {
297
305
  return stats;
298
306
  }
299
307
 
300
- async function compileFile(srcPath, outDir, filename, relativePath) {
308
+ async function compileFile(srcPath, outDir, filename, relativePath, root, envVars) {
301
309
  const ext = extname(filename);
302
310
  const loader = ext === '.tsx' ? 'tsx' : ext === '.ts' ? 'ts' : 'jsx';
303
311
 
304
312
  try {
305
313
  let code = await Bun.file(srcPath).text();
306
314
 
307
- // FIXED: Don't remove any imports - preserve them all
308
- // Only fix router imports to point to compiled location
315
+ // CRITICAL FIX: Remove ALL CSS imports before transpilation
316
+ code = removeCSSImports(code);
317
+
318
+ // Remove dotenv imports (not needed in browser)
319
+ code = removeDotenvImports(code);
320
+
321
+ // Inject environment variables
322
+ code = replaceEnvInCode(code, envVars);
323
+
309
324
  const outPath = join(outDir, filename.replace(/\.(jsx|tsx|ts)$/, '.js'));
310
- code = fixRouterImports(code, outPath, process.cwd());
325
+ code = fixRouterImports(code, outPath, root);
311
326
 
312
327
  const transpiler = new Bun.Transpiler({
313
328
  loader,
@@ -335,24 +350,44 @@ async function compileFile(srcPath, outDir, filename, relativePath) {
335
350
  }
336
351
  }
337
352
 
338
- // FIXED: New function - only fixes bertui/router imports
353
+ // NEW FUNCTION: Remove all CSS imports
354
+ function removeCSSImports(code) {
355
+ // Remove CSS imports (with or without quotes, single or double)
356
+ // Matches: import './styles.css', import "./styles.css", import "styles.css", import 'styles.css'
357
+ code = code.replace(/import\s+['"][^'"]*\.css['"];?\s*/g, '');
358
+
359
+ // Also remove bertui/styles imports
360
+ code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
361
+
362
+ return code;
363
+ }
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
+
339
379
  function fixRouterImports(code, outPath, root) {
340
380
  const buildDir = join(root, '.bertui', 'compiled');
341
381
  const routerPath = join(buildDir, 'router.js');
342
382
 
343
- // Calculate relative path from output file to router.js
344
383
  const relativeToRouter = relative(dirname(outPath), routerPath).replace(/\\/g, '/');
345
384
  const routerImport = relativeToRouter.startsWith('.') ? relativeToRouter : './' + relativeToRouter;
346
385
 
347
- // ONLY replace bertui/router imports
348
386
  code = code.replace(
349
387
  /from\s+['"]bertui\/router['"]/g,
350
388
  `from '${routerImport}'`
351
389
  );
352
390
 
353
- // Remove bertui/styles imports (CSS handled separately)
354
- code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
355
-
356
391
  return code;
357
392
  }
358
393
 
@@ -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
+ }