bertui 0.2.9 → 0.3.2

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/README.md CHANGED
@@ -2,110 +2,101 @@
2
2
 
3
3
  Lightning-fast React development powered by Bun.
4
4
 
5
+ ## ⚠️ Important Notice - CSS Animations Temporarily Unavailable
6
+
7
+ **The built-in CSS animation utilities have been temporarily removed** due to compatibility issues with `bun.build`. We're working on a solution and they will be back in an upcoming release.
8
+
9
+ **What this means:**
10
+ - The 15+ animation classes (`.fadein`, `.scalein`, `.bouncein`, etc.) are not currently available
11
+ - You can still use your own CSS animations or external libraries
12
+ - All other BertUI features work normally
13
+
14
+ **We apologize for any inconvenience caused.** This feature will return soon! 🚀
15
+
16
+ ---
17
+
5
18
  ## Features
6
19
 
7
20
  - ⚡ **Blazing Fast** - Built on Bun
8
- - 🎨 **Built-in Animations** - 15+ CSS utility classes
21
+ - 📁 **File-Based Routing** - Zero config routing
9
22
  - 🔥 **Hot Module Replacement** - Instant updates
10
23
  - 📦 **Zero Config** - Works out of the box
11
24
  - 🚀 **Production Ready** - Optimized builds
12
25
 
13
- ## Installation
26
+ ## Quick Start
27
+
28
+ ### Create New App (Recommended)
14
29
  ```bash
15
- bun add bertui react react-dom
30
+ bunx create-bertui my-app
31
+ cd my-app
32
+ bun run dev
16
33
  ```
17
34
 
18
- ## Usage
19
- ```javascript
20
- // src/main.jsx
21
- ;
22
- import React from 'react';
23
- import ReactDOM from 'react-dom/client';
35
+ This creates a complete BertUI project with:
36
+ - Pre-configured file structure
37
+ - Sample pages with routing
38
+ - Beautiful example components
39
+ - All dependencies installed
24
40
 
25
- ReactDOM.createRoot(document.getElementById('root')).render(
26
- <h1 className="split fadein">Hello BertUI!</h1>
27
- );
41
+ ### Manual Installation (Advanced)
42
+ If you want to configure everything yourself:
43
+ ```bash
44
+ bun add bertui react react-dom
28
45
  ```
29
46
 
47
+ Then you'll need to manually set up:
48
+ - Project structure (`src/pages/`, `src/main.jsx`, etc.)
49
+ - Router configuration
50
+ - Build configuration
51
+
52
+ **Note:** We recommend using `bunx create-bertui` for the best experience!
53
+
30
54
  ## Commands
31
55
  ```bash
32
56
  bertui dev # Start dev server
33
57
  bertui build # Build for production
34
58
  ```
35
59
 
36
- ## CSS Classes
37
-
38
- - `.split` - Split text animation
39
- - `.moveright` - Slide from left
40
- - `.moveleft` - Slide from right
41
- - `.fadein` - Fade in
42
- - `.scalein` - Scale in
43
- - `.bouncein` - Bounce in
44
- - `.slideup` - Slide up
45
- - `.slidedown` - Slide down
46
- - `.rotatein` - Rotate in
47
- - `.pulse` - Pulse animation
48
- - `.shake` - Shake animation
49
-
50
- # 🎉 BertUI Now Has File-Based Routing!
51
-
52
- ## What We Built
53
-
54
- I've added a **complete file-based routing system** to BertUI. Here's what's included:
55
-
56
- ### 📁 New Files to Add
60
+ ## File-Based Routing
57
61
 
58
- 1. **`src/router/router.js`** - Core routing logic
59
- - Scans `src/pages/` directory
60
- - Generates route definitions
61
- - Creates React Router component
62
- - Provides `Link` and `navigate` utilities
62
+ BertUI now has **complete file-based routing**! Here's what's included:
63
63
 
64
- 2. **`src/client/compiler.js`** (updated) - Enhanced compiler
65
- - Detects `pages/` directory
66
- - Auto-generates router code
67
- - Supports both routing and non-routing modes
64
+ ### 📁 Features
68
65
 
69
- 3. **`src/server/dev-server.js`** (updated) - Enhanced dev server
70
- - Serves SPA-style HTML for all routes
71
- - Notifies clients of route changes
72
- - Better HMR integration
73
-
74
- ## 🚀 Features
75
-
76
- ### ✅ File-Based Routing
66
+ #### File-Based Routing
77
67
  ```
78
68
  src/pages/index.jsx → /
79
69
  src/pages/about.jsx → /about
80
70
  src/pages/blog/index.jsx → /blog
81
71
  ```
82
72
 
83
- ### ✅ Dynamic Routes
73
+ #### ✅ Dynamic Routes
84
74
  ```
85
75
  src/pages/user/[id].jsx → /user/:id
86
76
  src/pages/blog/[slug].jsx → /blog/:slug
87
77
  src/pages/shop/[cat]/[prod].jsx → /shop/:cat/:prod
88
78
  ```
89
79
 
90
- ### ✅ Navigation Components
80
+ #### ✅ Navigation Components
91
81
  ```jsx
92
- import { Link, navigate } from '../.bertui/router';
82
+ import { Link, navigate } from 'bertui/router';
93
83
 
94
84
  // Link component
95
- <Link href="/about">About</Link>
85
+ <Link to="/about">About</Link>
96
86
 
97
87
  // Programmatic navigation
88
+ const { navigate } = useRouter();
98
89
  navigate('/dashboard');
99
90
  ```
100
91
 
101
- ### ✅ Route Parameters
92
+ #### ✅ Route Parameters
102
93
  ```jsx
103
94
  export default function UserProfile({ params }) {
104
95
  return <div>User ID: {params.id}</div>;
105
96
  }
106
97
  ```
107
98
 
108
- ### ✅ Backward Compatible
99
+ #### ✅ Backward Compatible
109
100
  - Still works with `src/main.jsx` if no `pages/` directory
110
101
  - Automatically detects routing mode
111
102
  - No breaking changes!
@@ -139,44 +130,31 @@ export default function UserProfile({ params }) {
139
130
  - Client-side routing handles navigation
140
131
  - HMR updates routes on file changes
141
132
 
142
- ## 🔧 Integration Steps
143
-
144
- ### 1. Add Router Files
145
- Copy these files to your BertUI project:
146
- - `src/router/router.js` (new)
147
- - `src/client/compiler.js` (replace)
148
- - `src/server/dev-server.js` (replace)
149
-
150
- ### 2. Update Dependencies
151
- No new dependencies needed! Uses existing React ecosystem.
152
-
153
- ### 3. Test It
154
- ```bash
155
- # Create example pages
156
- mkdir -p src/pages
157
- echo 'export default () => <h1>Home</h1>' > src/pages/index.jsx
158
- echo 'export default () => <h1>About</h1>' > src/pages/about.jsx
159
-
160
- # Start dev server
161
- bertui dev
162
- ```
163
-
164
- ### 4. Watch the Magic
165
- - Navigate to `http://localhost:3000` → Home page
166
- - Click links → No page reload!
167
- - Edit files → Instant HMR!
168
- - Check console → Route discovery logs
169
-
170
- ## 🎨 Works with BertUI Animations
133
+ ## 🎓 Usage Example
171
134
 
172
135
  ```jsx
136
+ // src/pages/index.jsx
137
+ import { Link } from 'bertui/router';
138
+
173
139
  export default function Home() {
174
140
  return (
175
141
  <div>
176
- <h1 className="split fadein" data-text="Welcome!">
177
- Welcome!
178
- </h1>
179
- <p className="moveright">Lightning fast! ⚡</p>
142
+ <h1>Welcome to My App!</h1>
143
+ <nav>
144
+ <Link to="/about">About</Link>
145
+ <Link to="/blog">Blog</Link>
146
+ <Link to="/user/123">My Profile</Link>
147
+ </nav>
148
+ </div>
149
+ );
150
+ }
151
+
152
+ // src/pages/user/[id].jsx
153
+ export default function UserProfile({ params }) {
154
+ return (
155
+ <div>
156
+ <h1>User {params.id}</h1>
157
+ <p>Profile page for user {params.id}</p>
180
158
  </div>
181
159
  );
182
160
  }
@@ -210,34 +188,13 @@ Update `build.js` to:
210
188
  - Create optimized bundles per route
211
189
  - Handle dynamic routes appropriately
212
190
 
213
- ## 🎓 Usage Example
214
-
215
- ```jsx
216
- // src/pages/index.jsx
217
- import { Link } from '../.bertui/router';
191
+ ## 🏁 Conclusion
218
192
 
219
- export default function Home() {
220
- return (
221
- <div className="fadein">
222
- <h1>Welcome to My App!</h1>
223
- <nav>
224
- <Link href="/about">About</Link>
225
- <Link href="/blog">Blog</Link>
226
- <Link href="/user/123">My Profile</Link>
227
- </nav>
228
- </div>
229
- );
230
- }
231
-
232
- // src/pages/user/[id].jsx
233
- export default function UserProfile({ params }) {
234
- return (
235
- <div className="scalein">
236
- <h1>User {params.id}</h1>
237
- <p>Profile page for user {params.id}</p>
238
- </div>
239
- );
240
- }
193
+ BertUI now has **production-ready file-based routing** that's:
194
+ - ⚡ **Fast** - Built on Bun
195
+ - 🎯 **Simple** - Zero config
196
+ - 💪 **Powerful** - Dynamic routes, params, navigation
197
+ - 🔥 **Modern** - HMR, code splitting, SPA
241
198
 
242
199
  ## License
243
200
 
package/bin/bertui.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bertui",
3
- "version": "0.2.9",
3
+ "version": "0.3.2",
4
4
  "description": "Lightning-fast React dev server powered by Bun and Elysia",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -46,9 +46,7 @@
46
46
  "dependencies": {
47
47
  "elysia": "^1.0.0",
48
48
  "ernest-logger": "latest",
49
- "postcss": "^8.4.32",
50
- "autoprefixer": "^10.4.16",
51
- "cssnano": "^6.0.2"
49
+ "lightningcss": "^1.30.2"
52
50
  },
53
51
  "peerDependencies": {
54
52
  "react": "^18.0.0 || ^19.0.0",
@@ -1,19 +1,16 @@
1
- // src/build/css-builder.js
2
1
  import { join } from 'path';
3
2
  import { existsSync, mkdirSync } from 'fs';
4
- import postcss from 'postcss';
5
- import autoprefixer from 'autoprefixer';
6
- import cssnano from 'cssnano';
3
+ import { transform } from 'lightningcss';
7
4
  import logger from '../logger/logger.js';
8
5
 
9
6
  /**
10
- * Build and minify CSS for production
7
+ * Build and minify CSS for production using Lightning CSS
11
8
  * @param {string} srcPath - Source CSS file path
12
9
  * @param {string} destPath - Destination CSS file path
13
10
  */
14
11
  export async function buildCSS(srcPath, destPath) {
15
12
  try {
16
- logger.info('Processing CSS...');
13
+ logger.info(`Processing CSS: ${srcPath.split('/').pop()}`);
17
14
 
18
15
  // Ensure destination directory exists
19
16
  const destDir = join(destPath, '..');
@@ -23,38 +20,38 @@ export async function buildCSS(srcPath, destPath) {
23
20
 
24
21
  // Read source CSS
25
22
  const css = await Bun.file(srcPath).text();
23
+ const originalSize = Buffer.byteLength(css);
26
24
 
27
- // Process with PostCSS
28
- const result = await postcss([
29
- autoprefixer(),
30
- cssnano({
31
- preset: ['default', {
32
- discardComments: { removeAll: true },
33
- normalizeWhitespace: true,
34
- colormin: true,
35
- minifyFontValues: true,
36
- minifySelectors: true,
37
- }]
38
- })
39
- ]).process(css, { from: srcPath, to: destPath });
25
+ // Transform with Lightning CSS (blazing fast)
26
+ const { code } = transform({
27
+ filename: srcPath,
28
+ code: Buffer.from(css),
29
+ minify: true,
30
+ sourceMap: false,
31
+ targets: {
32
+ // Support last 2 versions of major browsers
33
+ chrome: 90 << 16,
34
+ firefox: 88 << 16,
35
+ safari: 14 << 16,
36
+ edge: 90 << 16
37
+ },
38
+ drafts: {
39
+ nesting: true // Enable CSS nesting
40
+ }
41
+ });
40
42
 
41
43
  // Write minified CSS
42
- await Bun.write(destPath, result.css);
44
+ await Bun.write(destPath, code);
43
45
 
44
46
  // Calculate size reduction
45
- const originalSize = (Buffer.byteLength(css) / 1024).toFixed(2);
46
- const minifiedSize = (Buffer.byteLength(result.css) / 1024).toFixed(2);
47
- const reduction = ((1 - Buffer.byteLength(result.css) / Buffer.byteLength(css)) * 100).toFixed(1);
47
+ const minifiedSize = code.length;
48
+ const originalKB = (originalSize / 1024).toFixed(2);
49
+ const minifiedKB = (minifiedSize / 1024).toFixed(2);
50
+ const reduction = ((1 - minifiedSize / originalSize) * 100).toFixed(1);
48
51
 
49
- logger.success(`CSS minified: ${originalSize}KB → ${minifiedSize}KB (-${reduction}%)`);
52
+ logger.success(`CSS minified: ${originalKB}KB → ${minifiedKB}KB (-${reduction}%)`);
50
53
 
51
- if (result.warnings().length > 0) {
52
- result.warnings().forEach(warn => {
53
- logger.warn(warn.toString());
54
- });
55
- }
56
-
57
- return { success: true, size: minifiedSize };
54
+ return { success: true, size: minifiedKB };
58
55
  } catch (error) {
59
56
  logger.error(`CSS build failed: ${error.message}`);
60
57
  throw error;
package/src/build.js CHANGED
@@ -1,4 +1,3 @@
1
- // src/build.js
2
1
  import { join } from 'path';
3
2
  import { existsSync, mkdirSync, rmSync, cpSync, readdirSync, statSync } from 'fs';
4
3
  import { extname, relative, dirname } from 'path';
@@ -32,11 +31,9 @@ export async function buildProduction(options = {}) {
32
31
  await compileForBuild(root, buildDir);
33
32
  logger.success('Production compilation complete');
34
33
 
35
- // Step 2: Build CSS from BertUI library
36
- logger.info('Step 2: Building CSS...');
37
- const bertuiCssSource = join(import.meta.dir, 'styles/bertui.css');
38
- const bertuiCssDest = join(outDir, 'styles/bertui.min.css');
39
- await buildCSS(bertuiCssSource, bertuiCssDest);
34
+ // Step 2: Build CSS with Lightning CSS
35
+ logger.info('Step 2: Building CSS with Lightning CSS...');
36
+ await buildAllCSS(root, outDir);
40
37
 
41
38
  // Step 3: Copy public assets if they exist
42
39
  const publicDir = join(root, 'public');
@@ -49,7 +46,7 @@ export async function buildProduction(options = {}) {
49
46
  }
50
47
 
51
48
  // Step 4: Build JavaScript with Bun's bundler
52
- logger.info('Step 4: Bundling JavaScript...');
49
+ logger.info('Step 4: Bundling JavaScript with Bun...');
53
50
  const buildEntry = join(buildDir, 'main.js');
54
51
 
55
52
  if (!existsSync(buildEntry)) {
@@ -57,6 +54,7 @@ export async function buildProduction(options = {}) {
57
54
  process.exit(1);
58
55
  }
59
56
 
57
+ // FIXED: Let Bun handle ALL imports with proper externals
60
58
  const result = await Bun.build({
61
59
  entrypoints: [buildEntry],
62
60
  outdir: join(outDir, 'assets'),
@@ -69,7 +67,8 @@ export async function buildProduction(options = {}) {
69
67
  chunk: 'chunks/[name]-[hash].js',
70
68
  asset: '[name]-[hash].[ext]'
71
69
  },
72
- external: ['react', 'react-dom']
70
+ // FIXED: Use CDN externals - Bun handles tree shaking automatically
71
+ external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime']
73
72
  });
74
73
 
75
74
  if (!result.success) {
@@ -78,7 +77,7 @@ export async function buildProduction(options = {}) {
78
77
  process.exit(1);
79
78
  }
80
79
 
81
- logger.success('JavaScript bundled');
80
+ logger.success('JavaScript bundled with tree-shaking');
82
81
 
83
82
  // Step 5: Generate index.html
84
83
  logger.info('Step 5: Generating index.html...');
@@ -121,6 +120,27 @@ export async function buildProduction(options = {}) {
121
120
  }
122
121
  }
123
122
 
123
+ async function buildAllCSS(root, outDir) {
124
+ const srcStylesDir = join(root, 'src', 'styles');
125
+ const bertuiCssSource = join(import.meta.dir, 'styles/bertui.css');
126
+ const stylesOutDir = join(outDir, 'styles');
127
+
128
+ mkdirSync(stylesOutDir, { recursive: true });
129
+
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
+ if (existsSync(srcStylesDir)) {
135
+ const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
136
+ for (const cssFile of cssFiles) {
137
+ const srcPath = join(srcStylesDir, cssFile);
138
+ const destPath = join(stylesOutDir, cssFile.replace('.css', '.min.css'));
139
+ await buildCSS(srcPath, destPath);
140
+ }
141
+ }
142
+ }
143
+
124
144
  async function compileForBuild(root, buildDir) {
125
145
  const srcDir = join(root, 'src');
126
146
  const pagesDir = join(srcDir, 'pages');
@@ -160,6 +180,10 @@ async function discoverRoutes(pagesDir) {
160
180
  await scanDirectory(fullPath, relativePath);
161
181
  } else if (entry.isFile()) {
162
182
  const ext = extname(entry.name);
183
+
184
+ // FIXED: Ignore CSS files
185
+ if (ext === '.css') continue;
186
+
163
187
  if (['.jsx', '.tsx', '.js', '.ts'].includes(ext)) {
164
188
  const fileName = entry.name.replace(ext, '');
165
189
 
@@ -345,6 +369,9 @@ async function compileBuildDirectory(srcDir, buildDir, root) {
345
369
  } else {
346
370
  const ext = extname(file);
347
371
 
372
+ // FIXED: Skip CSS files in build
373
+ if (ext === '.css') continue;
374
+
348
375
  if (['.jsx', '.tsx', '.ts'].includes(ext)) {
349
376
  await compileBuildFile(srcPath, buildDir, file, root);
350
377
  } else if (ext === '.js') {
@@ -395,6 +422,7 @@ async function compileBuildFile(srcPath, buildDir, filename, root) {
395
422
  }
396
423
  }
397
424
 
425
+ // FIXED: Only fix router imports, preserve all others
398
426
  function fixBuildImports(code, srcPath, outPath, root) {
399
427
  // Remove bertui/styles imports
400
428
  code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
@@ -406,7 +434,7 @@ function fixBuildImports(code, srcPath, outPath, root) {
406
434
  const relativeToRouter = relative(dirname(outPath), routerPath).replace(/\\/g, '/');
407
435
  const routerImport = relativeToRouter.startsWith('.') ? relativeToRouter : './' + relativeToRouter;
408
436
 
409
- // Replace bertui/router imports
437
+ // ONLY replace bertui/router imports
410
438
  code = code.replace(
411
439
  /from\s+['"]bertui\/router['"]/g,
412
440
  `from '${routerImport}'`
@@ -451,7 +479,9 @@ async function generateProductionHTML(root, outDir, buildResult) {
451
479
  {
452
480
  "imports": {
453
481
  "react": "https://esm.sh/react@18.2.0",
454
- "react-dom": "https://esm.sh/react-dom@18.2.0"
482
+ "react-dom": "https://esm.sh/react-dom@18.2.0",
483
+ "react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
484
+ "react/jsx-runtime": "https://esm.sh/react@18.2.0/jsx-runtime"
455
485
  }
456
486
  }
457
487
  </script>
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
2
- import { join, extname, relative } from 'path';
2
+ import { join, extname, relative, dirname } from 'path';
3
3
  import logger from '../logger/logger.js';
4
4
 
5
5
  export async function compileProject(root) {
@@ -64,6 +64,14 @@ async function discoverRoutes(pagesDir) {
64
64
  await scanDirectory(fullPath, relativePath);
65
65
  } else if (entry.isFile()) {
66
66
  const ext = extname(entry.name);
67
+
68
+ // FIXED: Ignore CSS files completely
69
+ if (ext === '.css') {
70
+ logger.debug(`Skipping CSS file: ${relativePath}`);
71
+ continue;
72
+ }
73
+
74
+ // Only process valid page files
67
75
  if (['.jsx', '.tsx', '.js', '.ts'].includes(ext)) {
68
76
  const fileName = entry.name.replace(ext, '');
69
77
 
@@ -255,14 +263,26 @@ async function compileDirectory(srcDir, outDir, root) {
255
263
  const ext = extname(file);
256
264
  const relativePath = relative(join(root, 'src'), srcPath);
257
265
 
258
- if (['.jsx', '.tsx', '.ts'].includes(ext)) {
266
+ // FIXED: Handle CSS files properly - copy to styles output
267
+ if (ext === '.css') {
268
+ const stylesOutDir = join(root, '.bertui', 'styles');
269
+ if (!existsSync(stylesOutDir)) {
270
+ mkdirSync(stylesOutDir, { recursive: true });
271
+ }
272
+ const cssOutPath = join(stylesOutDir, file);
273
+ await Bun.write(cssOutPath, Bun.file(srcPath));
274
+ logger.debug(`Copied CSS: ${relativePath}`);
275
+ stats.files++;
276
+ } else if (['.jsx', '.tsx', '.ts'].includes(ext)) {
259
277
  await compileFile(srcPath, outDir, file, relativePath);
260
278
  stats.files++;
261
279
  } else if (ext === '.js') {
262
280
  const outPath = join(outDir, file);
263
281
  let code = await Bun.file(srcPath).text();
264
282
 
265
- code = fixImports(code);
283
+ // FIXED: Don't modify imports - let Bun handle them
284
+ // Only fix router imports
285
+ code = fixRouterImports(code, outPath, root);
266
286
 
267
287
  await Bun.write(outPath, code);
268
288
  logger.debug(`Copied: ${relativePath}`);
@@ -284,7 +304,10 @@ async function compileFile(srcPath, outDir, filename, relativePath) {
284
304
  try {
285
305
  let code = await Bun.file(srcPath).text();
286
306
 
287
- code = fixImports(code);
307
+ // FIXED: Don't remove any imports - preserve them all
308
+ // Only fix router imports to point to compiled location
309
+ const outPath = join(outDir, filename.replace(/\.(jsx|tsx|ts)$/, '.js'));
310
+ code = fixRouterImports(code, outPath, process.cwd());
288
311
 
289
312
  const transpiler = new Bun.Transpiler({
290
313
  loader,
@@ -304,29 +327,31 @@ async function compileFile(srcPath, outDir, filename, relativePath) {
304
327
 
305
328
  compiled = fixRelativeImports(compiled);
306
329
 
307
- const outFilename = filename.replace(/\.(jsx|tsx|ts)$/, '.js');
308
- const outPath = join(outDir, outFilename);
309
-
310
330
  await Bun.write(outPath, compiled);
311
- logger.debug(`Compiled: ${relativePath} → ${outFilename}`);
331
+ logger.debug(`Compiled: ${relativePath} → ${filename.replace(/\.(jsx|tsx|ts)$/, '.js')}`);
312
332
  } catch (error) {
313
333
  logger.error(`Failed to compile ${relativePath}: ${error.message}`);
314
334
  throw error;
315
335
  }
316
336
  }
317
337
 
318
- function fixImports(code) {
319
- code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
338
+ // FIXED: New function - only fixes bertui/router imports
339
+ function fixRouterImports(code, outPath, root) {
340
+ const buildDir = join(root, '.bertui', 'compiled');
341
+ const routerPath = join(buildDir, 'router.js');
320
342
 
343
+ // Calculate relative path from output file to router.js
344
+ const relativeToRouter = relative(dirname(outPath), routerPath).replace(/\\/g, '/');
345
+ const routerImport = relativeToRouter.startsWith('.') ? relativeToRouter : './' + relativeToRouter;
346
+
347
+ // ONLY replace bertui/router imports
321
348
  code = code.replace(
322
349
  /from\s+['"]bertui\/router['"]/g,
323
- "from '/compiled/router.js'"
350
+ `from '${routerImport}'`
324
351
  );
325
352
 
326
- code = code.replace(
327
- /from\s+['"]\.\.\/\.bertui\/compiled\/([^'"]+)['"]/g,
328
- "from '/compiled/$1'"
329
- );
353
+ // Remove bertui/styles imports (CSS handled separately)
354
+ code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
330
355
 
331
356
  return code;
332
357
  }
@@ -342,5 +367,4 @@ function fixRelativeImports(code) {
342
367
  });
343
368
 
344
369
  return code;
345
- }
346
-
370
+ }
@@ -10,6 +10,7 @@ export async function startDevServer(options = {}) {
10
10
  const port = parseInt(options.port) || 3000;
11
11
  const root = options.root || process.cwd();
12
12
  const compiledDir = join(root, '.bertui', 'compiled');
13
+ const stylesDir = join(root, '.bertui', 'styles');
13
14
 
14
15
  const config = await loadConfig(root);
15
16
 
@@ -49,6 +50,21 @@ export async function startDevServer(options = {}) {
49
50
  }
50
51
  }
51
52
 
53
+ // FIXED: Handle CSS files from .bertui/styles
54
+ if (path.startsWith('styles/') && path.endsWith('.css')) {
55
+ const cssPath = join(stylesDir, path.replace('styles/', ''));
56
+ const file = Bun.file(cssPath);
57
+
58
+ if (await file.exists()) {
59
+ return new Response(await file.text(), {
60
+ headers: {
61
+ 'Content-Type': 'text/css',
62
+ 'Cache-Control': 'no-store'
63
+ }
64
+ });
65
+ }
66
+ }
67
+
52
68
  if (path.startsWith('public/')) {
53
69
  const publicDir = join(root, 'public');
54
70
  const filepath = join(publicDir, path.replace('public/', ''));
@@ -132,6 +148,24 @@ ws.onclose = () => {
132
148
  });
133
149
  })
134
150
 
151
+ // FIXED: Serve CSS from .bertui/styles
152
+ .get('/styles/*', async ({ params, set }) => {
153
+ const filepath = join(stylesDir, params['*']);
154
+ const file = Bun.file(filepath);
155
+
156
+ if (!await file.exists()) {
157
+ set.status = 404;
158
+ return 'CSS file not found';
159
+ }
160
+
161
+ return new Response(await file.text(), {
162
+ headers: {
163
+ 'Content-Type': 'text/css',
164
+ 'Cache-Control': 'no-store'
165
+ }
166
+ });
167
+ })
168
+
135
169
  .get('/public/*', async ({ params, set }) => {
136
170
  const publicDir = join(root, 'public');
137
171
  const filepath = join(publicDir, params['*']);
@@ -165,6 +199,15 @@ ws.onclose = () => {
165
199
  function serveHTML(root, hasRouter, config) {
166
200
  const meta = config.meta || {};
167
201
 
202
+ // Find user's CSS files
203
+ const srcStylesDir = join(root, 'src', 'styles');
204
+ let userStylesheets = '';
205
+
206
+ if (existsSync(srcStylesDir)) {
207
+ const cssFiles = require('fs').readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
208
+ userStylesheets = cssFiles.map(f => ` <link rel="stylesheet" href="/styles/${f}">`).join('\n');
209
+ }
210
+
168
211
  const html = `
169
212
  <!DOCTYPE html>
170
213
  <html lang="${meta.lang || 'en'}">
@@ -184,6 +227,8 @@ function serveHTML(root, hasRouter, config) {
184
227
 
185
228
  <link rel="icon" type="image/svg+xml" href="/public/favicon.svg">
186
229
 
230
+ ${userStylesheets}
231
+
187
232
  <script type="importmap">
188
233
  {
189
234
  "imports": {