bertui 0.2.6 → 0.2.8

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.2.6",
3
+ "version": "0.2.8",
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,52 +1,64 @@
1
1
  // src/build.js
2
2
  import { join } from 'path';
3
- import { existsSync, mkdirSync, rmSync, cpSync } from 'fs';
3
+ import { existsSync, mkdirSync, rmSync, cpSync, readdirSync, statSync } from 'fs';
4
+ import { extname, relative, dirname } from 'path';
4
5
  import logger from './logger/logger.js';
5
6
  import { buildCSS } from './build/css-builder.js';
6
7
 
7
8
  export async function buildProduction(options = {}) {
8
9
  const root = options.root || process.cwd();
10
+ const buildDir = join(root, '.bertuibuild');
9
11
  const outDir = join(root, 'dist');
10
12
 
11
13
  logger.bigLog('BUILDING FOR PRODUCTION', { color: 'green' });
12
14
 
13
- // Clean dist folder
15
+ // Clean folders
16
+ if (existsSync(buildDir)) {
17
+ rmSync(buildDir, { recursive: true });
18
+ }
14
19
  if (existsSync(outDir)) {
15
20
  rmSync(outDir, { recursive: true });
16
21
  logger.info('Cleaned dist/');
17
22
  }
23
+
24
+ mkdirSync(buildDir, { recursive: true });
18
25
  mkdirSync(outDir, { recursive: true });
19
26
 
20
27
  const startTime = Date.now();
21
28
 
22
29
  try {
23
- // Step 1: Build CSS from BertUI library
24
- logger.info('Step 1: Building CSS...');
30
+ // Step 1: Compile for production
31
+ logger.info('Step 1: Compiling for production...');
32
+ await compileForBuild(root, buildDir);
33
+ logger.success('Production compilation complete');
34
+
35
+ // Step 2: Build CSS from BertUI library
36
+ logger.info('Step 2: Building CSS...');
25
37
  const bertuiCssSource = join(import.meta.dir, 'styles/bertui.css');
26
38
  const bertuiCssDest = join(outDir, 'styles/bertui.min.css');
27
39
  await buildCSS(bertuiCssSource, bertuiCssDest);
28
40
 
29
- // Step 2: Copy public assets if they exist
41
+ // Step 3: Copy public assets if they exist
30
42
  const publicDir = join(root, 'public');
31
43
  if (existsSync(publicDir)) {
32
- logger.info('Step 2: Copying public assets...');
44
+ logger.info('Step 3: Copying public assets...');
33
45
  cpSync(publicDir, outDir, { recursive: true });
34
46
  logger.success('Public assets copied');
35
47
  } else {
36
- logger.info('Step 2: No public directory found, skipping...');
48
+ logger.info('Step 3: No public directory found, skipping...');
37
49
  }
38
50
 
39
- // Step 3: Build JavaScript with Bun's bundler
40
- logger.info('Step 3: Bundling JavaScript...');
41
- const mainEntry = join(root, 'src/main.jsx');
51
+ // Step 4: Build JavaScript with Bun's bundler
52
+ logger.info('Step 4: Bundling JavaScript...');
53
+ const buildEntry = join(buildDir, 'main.js');
42
54
 
43
- if (!existsSync(mainEntry)) {
44
- logger.error('Entry point not found: src/main.jsx');
55
+ if (!existsSync(buildEntry)) {
56
+ logger.error('Build entry point not found: .bertuibuild/main.js');
45
57
  process.exit(1);
46
58
  }
47
59
 
48
60
  const result = await Bun.build({
49
- entrypoints: [mainEntry],
61
+ entrypoints: [buildEntry],
50
62
  outdir: join(outDir, 'assets'),
51
63
  target: 'browser',
52
64
  minify: true,
@@ -57,7 +69,7 @@ export async function buildProduction(options = {}) {
57
69
  chunk: 'chunks/[name]-[hash].js',
58
70
  asset: '[name]-[hash].[ext]'
59
71
  },
60
- external: [] // Don't externalize anything for browser builds
72
+ external: ['react', 'react-dom']
61
73
  });
62
74
 
63
75
  if (!result.success) {
@@ -68,10 +80,14 @@ export async function buildProduction(options = {}) {
68
80
 
69
81
  logger.success('JavaScript bundled');
70
82
 
71
- // Step 4: Generate index.html
72
- logger.info('Step 4: Generating index.html...');
83
+ // Step 5: Generate index.html
84
+ logger.info('Step 5: Generating index.html...');
73
85
  await generateProductionHTML(root, outDir, result);
74
86
 
87
+ // Step 6: Clean up build folder
88
+ rmSync(buildDir, { recursive: true });
89
+ logger.info('Cleaned up .bertuibuild/');
90
+
75
91
  const duration = Date.now() - startTime;
76
92
  logger.success(`✨ Build complete in ${duration}ms`);
77
93
  logger.info(`📦 Output: ${outDir}`);
@@ -87,12 +103,324 @@ export async function buildProduction(options = {}) {
87
103
  if (error.stack) {
88
104
  logger.error(error.stack);
89
105
  }
106
+
107
+ // Clean up on error
108
+ if (existsSync(buildDir)) {
109
+ rmSync(buildDir, { recursive: true });
110
+ }
111
+
90
112
  process.exit(1);
91
113
  }
92
114
  }
93
115
 
116
+ async function compileForBuild(root, buildDir) {
117
+ const srcDir = join(root, 'src');
118
+ const pagesDir = join(srcDir, 'pages');
119
+
120
+ if (!existsSync(srcDir)) {
121
+ throw new Error('src/ directory not found!');
122
+ }
123
+
124
+ // Discover routes
125
+ let routes = [];
126
+ if (existsSync(pagesDir)) {
127
+ routes = await discoverRoutes(pagesDir);
128
+ logger.info(`Found ${routes.length} routes`);
129
+ }
130
+
131
+ // Compile all source files
132
+ await compileBuildDirectory(srcDir, buildDir, root);
133
+
134
+ // Generate router if we have routes
135
+ if (routes.length > 0) {
136
+ await generateBuildRouter(routes, buildDir);
137
+ logger.info('Generated router for build');
138
+ }
139
+ }
140
+
141
+ async function discoverRoutes(pagesDir) {
142
+ const routes = [];
143
+
144
+ async function scanDirectory(dir, basePath = '') {
145
+ const entries = readdirSync(dir, { withFileTypes: true });
146
+
147
+ for (const entry of entries) {
148
+ const fullPath = join(dir, entry.name);
149
+ const relativePath = join(basePath, entry.name);
150
+
151
+ if (entry.isDirectory()) {
152
+ await scanDirectory(fullPath, relativePath);
153
+ } else if (entry.isFile()) {
154
+ const ext = extname(entry.name);
155
+ if (['.jsx', '.tsx', '.js', '.ts'].includes(ext)) {
156
+ const fileName = entry.name.replace(ext, '');
157
+
158
+ let route = '/' + relativePath.replace(/\\/g, '/').replace(ext, '');
159
+
160
+ if (fileName === 'index') {
161
+ route = route.replace('/index', '') || '/';
162
+ }
163
+
164
+ const isDynamic = fileName.includes('[') && fileName.includes(']');
165
+ const type = isDynamic ? 'dynamic' : 'static';
166
+
167
+ routes.push({
168
+ route: route === '' ? '/' : route,
169
+ file: relativePath.replace(/\\/g, '/'),
170
+ path: fullPath,
171
+ type
172
+ });
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ await scanDirectory(pagesDir);
179
+
180
+ routes.sort((a, b) => {
181
+ if (a.type === b.type) {
182
+ return a.route.localeCompare(b.route);
183
+ }
184
+ return a.type === 'static' ? -1 : 1;
185
+ });
186
+
187
+ return routes;
188
+ }
189
+
190
+ async function generateBuildRouter(routes, buildDir) {
191
+ const imports = routes.map((route, i) => {
192
+ const componentName = `Page${i}`;
193
+ const importPath = `./pages/${route.file.replace(/\.(jsx|tsx|ts)$/, '.js')}`;
194
+ return `import ${componentName} from '${importPath}';`;
195
+ }).join('\n');
196
+
197
+ const routeConfigs = routes.map((route, i) => {
198
+ const componentName = `Page${i}`;
199
+ return ` { path: '${route.route}', component: ${componentName}, type: '${route.type}' }`;
200
+ }).join(',\n');
201
+
202
+ const routerCode = `import React, { useState, useEffect, createContext, useContext } from 'react';
203
+
204
+ const RouterContext = createContext(null);
205
+
206
+ export function useRouter() {
207
+ const context = useContext(RouterContext);
208
+ if (!context) {
209
+ throw new Error('useRouter must be used within a Router component');
210
+ }
211
+ return context;
212
+ }
213
+
214
+ export function Router({ routes }) {
215
+ const [currentRoute, setCurrentRoute] = useState(null);
216
+ const [params, setParams] = useState({});
217
+
218
+ useEffect(() => {
219
+ matchAndSetRoute(window.location.pathname);
220
+
221
+ const handlePopState = () => {
222
+ matchAndSetRoute(window.location.pathname);
223
+ };
224
+
225
+ window.addEventListener('popstate', handlePopState);
226
+ return () => window.removeEventListener('popstate', handlePopState);
227
+ }, [routes]);
228
+
229
+ function matchAndSetRoute(pathname) {
230
+ for (const route of routes) {
231
+ if (route.type === 'static' && route.path === pathname) {
232
+ setCurrentRoute(route);
233
+ setParams({});
234
+ return;
235
+ }
236
+ }
237
+
238
+ for (const route of routes) {
239
+ if (route.type === 'dynamic') {
240
+ const pattern = route.path.replace(/\\[([^\\]]+)\\]/g, '([^/]+)');
241
+ const regex = new RegExp('^' + pattern + '$');
242
+ const match = pathname.match(regex);
243
+
244
+ if (match) {
245
+ const paramNames = [...route.path.matchAll(/\\[([^\\]]+)\\]/g)].map(m => m[1]);
246
+ const extractedParams = {};
247
+ paramNames.forEach((name, i) => {
248
+ extractedParams[name] = match[i + 1];
249
+ });
250
+
251
+ setCurrentRoute(route);
252
+ setParams(extractedParams);
253
+ return;
254
+ }
255
+ }
256
+ }
257
+
258
+ setCurrentRoute(null);
259
+ setParams({});
260
+ }
261
+
262
+ function navigate(path) {
263
+ window.history.pushState({}, '', path);
264
+ matchAndSetRoute(path);
265
+ }
266
+
267
+ const routerValue = {
268
+ currentRoute,
269
+ params,
270
+ navigate,
271
+ pathname: window.location.pathname
272
+ };
273
+
274
+ const Component = currentRoute?.component;
275
+
276
+ return React.createElement(
277
+ RouterContext.Provider,
278
+ { value: routerValue },
279
+ Component ? React.createElement(Component, { params }) : React.createElement(NotFound, null)
280
+ );
281
+ }
282
+
283
+ export function Link({ to, children, ...props }) {
284
+ const { navigate } = useRouter();
285
+
286
+ function handleClick(e) {
287
+ e.preventDefault();
288
+ navigate(to);
289
+ }
290
+
291
+ return React.createElement('a', { href: to, onClick: handleClick, ...props }, children);
292
+ }
293
+
294
+ function NotFound() {
295
+ return React.createElement(
296
+ 'div',
297
+ {
298
+ style: {
299
+ display: 'flex',
300
+ flexDirection: 'column',
301
+ alignItems: 'center',
302
+ justifyContent: 'center',
303
+ minHeight: '100vh',
304
+ fontFamily: 'system-ui'
305
+ }
306
+ },
307
+ React.createElement('h1', { style: { fontSize: '6rem', margin: 0 } }, '404'),
308
+ React.createElement('p', { style: { fontSize: '1.5rem', color: '#666' } }, 'Page not found'),
309
+ React.createElement('a', {
310
+ href: '/',
311
+ style: { color: '#10b981', textDecoration: 'none', fontSize: '1.2rem' }
312
+ }, 'Go home')
313
+ );
314
+ }
315
+
316
+ ${imports}
317
+
318
+ export const routes = [
319
+ ${routeConfigs}
320
+ ];
321
+ `;
322
+
323
+ await Bun.write(join(buildDir, 'router.js'), routerCode);
324
+ }
325
+
326
+ async function compileBuildDirectory(srcDir, buildDir, root) {
327
+ const files = readdirSync(srcDir);
328
+
329
+ for (const file of files) {
330
+ const srcPath = join(srcDir, file);
331
+ const stat = statSync(srcPath);
332
+
333
+ if (stat.isDirectory()) {
334
+ const subBuildDir = join(buildDir, file);
335
+ mkdirSync(subBuildDir, { recursive: true });
336
+ await compileBuildDirectory(srcPath, subBuildDir, root);
337
+ } else {
338
+ const ext = extname(file);
339
+
340
+ if (['.jsx', '.tsx', '.ts'].includes(ext)) {
341
+ await compileBuildFile(srcPath, buildDir, file, root);
342
+ } else if (ext === '.js') {
343
+ const outPath = join(buildDir, file);
344
+ let code = await Bun.file(srcPath).text();
345
+ code = fixBuildImports(code, srcPath, outPath, root);
346
+ await Bun.write(outPath, code);
347
+ }
348
+ }
349
+ }
350
+ }
351
+
352
+ async function compileBuildFile(srcPath, buildDir, filename, root) {
353
+ const ext = extname(filename);
354
+ const loader = ext === '.tsx' ? 'tsx' : ext === '.ts' ? 'ts' : 'jsx';
355
+
356
+ try {
357
+ let code = await Bun.file(srcPath).text();
358
+
359
+ const outFilename = filename.replace(/\.(jsx|tsx|ts)$/, '.js');
360
+ const outPath = join(buildDir, outFilename);
361
+
362
+ code = fixBuildImports(code, srcPath, outPath, root);
363
+
364
+ const transpiler = new Bun.Transpiler({
365
+ loader,
366
+ tsconfig: {
367
+ compilerOptions: {
368
+ jsx: 'react',
369
+ jsxFactory: 'React.createElement',
370
+ jsxFragmentFactory: 'React.Fragment'
371
+ }
372
+ }
373
+ });
374
+
375
+ let compiled = await transpiler.transform(code);
376
+
377
+ if (!compiled.includes('import React') && (compiled.includes('React.createElement') || compiled.includes('React.Fragment'))) {
378
+ compiled = `import React from 'react';\n${compiled}`;
379
+ }
380
+
381
+ compiled = fixRelativeImports(compiled);
382
+
383
+ await Bun.write(outPath, compiled);
384
+ } catch (error) {
385
+ logger.error(`Failed to compile ${filename}: ${error.message}`);
386
+ throw error;
387
+ }
388
+ }
389
+
390
+ function fixBuildImports(code, srcPath, outPath, root) {
391
+ // Remove bertui/styles imports
392
+ code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
393
+
394
+ const buildDir = join(root, '.bertuibuild');
395
+ const routerPath = join(buildDir, 'router.js');
396
+
397
+ // Calculate relative path from output file to router.js
398
+ const relativeToRouter = relative(dirname(outPath), routerPath).replace(/\\/g, '/');
399
+ const routerImport = relativeToRouter.startsWith('.') ? relativeToRouter : './' + relativeToRouter;
400
+
401
+ // Replace bertui/router imports
402
+ code = code.replace(
403
+ /from\s+['"]bertui\/router['"]/g,
404
+ `from '${routerImport}'`
405
+ );
406
+
407
+ return code;
408
+ }
409
+
410
+ function fixRelativeImports(code) {
411
+ const importRegex = /from\s+['"](\.\.[\/\\]|\.\/)((?:[^'"]+?)(?<!\.js|\.jsx|\.ts|\.tsx|\.json))['"];?/g;
412
+
413
+ code = code.replace(importRegex, (match, prefix, path) => {
414
+ if (path.endsWith('/') || /\.\w+$/.test(path)) {
415
+ return match;
416
+ }
417
+ return `from '${prefix}${path}.js';`;
418
+ });
419
+
420
+ return code;
421
+ }
422
+
94
423
  async function generateProductionHTML(root, outDir, buildResult) {
95
- // Find the main bundle
96
424
  const mainBundle = buildResult.outputs.find(o =>
97
425
  o.path.includes('main') && o.kind === 'entry-point'
98
426
  );
@@ -111,6 +439,14 @@ async function generateProductionHTML(root, outDir, buildResult) {
111
439
  <meta name="description" content="Built with BertUI - Lightning fast React development">
112
440
  <title>BertUI App</title>
113
441
  <link rel="stylesheet" href="/styles/bertui.min.css">
442
+ <script type="importmap">
443
+ {
444
+ "imports": {
445
+ "react": "https://esm.sh/react@18.2.0",
446
+ "react-dom": "https://esm.sh/react-dom@18.2.0"
447
+ }
448
+ }
449
+ </script>
114
450
  </head>
115
451
  <body>
116
452
  <div id="root"></div>
@@ -120,4 +456,4 @@ async function generateProductionHTML(root, outDir, buildResult) {
120
456
 
121
457
  await Bun.write(join(outDir, 'index.html'), html);
122
458
  logger.success('Generated index.html');
123
- }
459
+ }
@@ -1,349 +1,3 @@
1
- // import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
2
- // import { join, extname, relative } from 'path';
3
- // import logger from '../logger/logger.js';
4
-
5
- // export async function compileProject(root) {
6
- // logger.bigLog('COMPILING PROJECT', { color: 'blue' });
7
-
8
- // const srcDir = join(root, 'src');
9
- // const pagesDir = join(srcDir, 'pages');
10
- // const outDir = join(root, '.bertui', 'compiled');
11
-
12
- // if (!existsSync(srcDir)) {
13
- // logger.error('src/ directory not found!');
14
- // process.exit(1);
15
- // }
16
-
17
- // if (!existsSync(outDir)) {
18
- // mkdirSync(outDir, { recursive: true });
19
- // logger.info('Created .bertui/compiled/');
20
- // }
21
-
22
- // let routes = [];
23
- // if (existsSync(pagesDir)) {
24
- // routes = await discoverRoutes(pagesDir);
25
- // logger.info(`Discovered ${routes.length} routes`);
26
-
27
- // if (routes.length > 0) {
28
- // logger.bigLog('ROUTES DISCOVERED', { color: 'blue' });
29
- // logger.table(routes.map((r, i) => ({
30
- // '': i,
31
- // route: r.route,
32
- // file: r.file,
33
- // type: r.type
34
- // })));
35
- // }
36
- // }
37
-
38
- // const startTime = Date.now();
39
- // const stats = await compileDirectory(srcDir, outDir, root);
40
- // const duration = Date.now() - startTime;
41
-
42
- // if (routes.length > 0) {
43
- // await generateRouter(routes, outDir, root);
44
- // logger.info('Generated router.js');
45
- // }
46
-
47
- // logger.success(`Compiled ${stats.files} files in ${duration}ms`);
48
- // logger.info(`Output: ${outDir}`);
49
-
50
- // return { outDir, stats, routes };
51
- // }
52
-
53
- // async function discoverRoutes(pagesDir) {
54
- // const routes = [];
55
-
56
- // async function scanDirectory(dir, basePath = '') {
57
- // const entries = readdirSync(dir, { withFileTypes: true });
58
-
59
- // for (const entry of entries) {
60
- // const fullPath = join(dir, entry.name);
61
- // const relativePath = join(basePath, entry.name);
62
-
63
- // if (entry.isDirectory()) {
64
- // await scanDirectory(fullPath, relativePath);
65
- // } else if (entry.isFile()) {
66
- // const ext = extname(entry.name);
67
- // if (['.jsx', '.tsx', '.js', '.ts'].includes(ext)) {
68
- // const fileName = entry.name.replace(ext, '');
69
-
70
- // let route = '/' + relativePath.replace(/\\/g, '/').replace(ext, '');
71
-
72
- // if (fileName === 'index') {
73
- // route = route.replace('/index', '') || '/';
74
- // }
75
-
76
- // const isDynamic = fileName.includes('[') && fileName.includes(']');
77
- // const type = isDynamic ? 'dynamic' : 'static';
78
-
79
- // routes.push({
80
- // route: route === '' ? '/' : route,
81
- // file: relativePath.replace(/\\/g, '/'),
82
- // path: fullPath,
83
- // type
84
- // });
85
- // }
86
- // }
87
- // }
88
- // }
89
-
90
- // await scanDirectory(pagesDir);
91
-
92
- // routes.sort((a, b) => {
93
- // if (a.type === b.type) {
94
- // return a.route.localeCompare(b.route);
95
- // }
96
- // return a.type === 'static' ? -1 : 1;
97
- // });
98
-
99
- // return routes;
100
- // }
101
-
102
- // async function generateRouter(routes, outDir, root) {
103
- // const imports = routes.map((route, i) => {
104
- // const componentName = `Page${i}`;
105
- // const importPath = `./pages/${route.file.replace(/\.(jsx|tsx|ts)$/, '.js')}`;
106
- // return `import ${componentName} from '${importPath}';`;
107
- // }).join('\n');
108
-
109
- // const routeConfigs = routes.map((route, i) => {
110
- // const componentName = `Page${i}`;
111
- // return ` { path: '${route.route}', component: ${componentName}, type: '${route.type}' }`;
112
- // }).join(',\n');
113
-
114
- // const routerComponentCode = `import React, { useState, useEffect, createContext, useContext } from 'react';
115
-
116
- // const RouterContext = createContext(null);
117
-
118
- // export function useRouter() {
119
- // const context = useContext(RouterContext);
120
- // if (!context) {
121
- // throw new Error('useRouter must be used within a Router component');
122
- // }
123
- // return context;
124
- // }
125
-
126
- // export function Router({ routes }) {
127
- // const [currentRoute, setCurrentRoute] = useState(null);
128
- // const [params, setParams] = useState({});
129
-
130
- // useEffect(() => {
131
- // matchAndSetRoute(window.location.pathname);
132
-
133
- // const handlePopState = () => {
134
- // matchAndSetRoute(window.location.pathname);
135
- // };
136
-
137
- // window.addEventListener('popstate', handlePopState);
138
- // return () => window.removeEventListener('popstate', handlePopState);
139
- // }, [routes]);
140
-
141
- // function matchAndSetRoute(pathname) {
142
- // for (const route of routes) {
143
- // if (route.type === 'static' && route.path === pathname) {
144
- // setCurrentRoute(route);
145
- // setParams({});
146
- // return;
147
- // }
148
- // }
149
-
150
- // for (const route of routes) {
151
- // if (route.type === 'dynamic') {
152
- // const pattern = route.path.replace(/\\[([^\\]]+)\\]/g, '([^/]+)');
153
- // const regex = new RegExp('^' + pattern + '$');
154
- // const match = pathname.match(regex);
155
-
156
- // if (match) {
157
- // const paramNames = [...route.path.matchAll(/\\[([^\\]]+)\\]/g)].map(m => m[1]);
158
- // const extractedParams = {};
159
- // paramNames.forEach((name, i) => {
160
- // extractedParams[name] = match[i + 1];
161
- // });
162
-
163
- // setCurrentRoute(route);
164
- // setParams(extractedParams);
165
- // return;
166
- // }
167
- // }
168
- // }
169
-
170
- // setCurrentRoute(null);
171
- // setParams({});
172
- // }
173
-
174
- // function navigate(path) {
175
- // window.history.pushState({}, '', path);
176
- // matchAndSetRoute(path);
177
- // }
178
-
179
- // const routerValue = {
180
- // currentRoute,
181
- // params,
182
- // navigate,
183
- // pathname: window.location.pathname
184
- // };
185
-
186
- // const Component = currentRoute?.component;
187
-
188
- // return React.createElement(
189
- // RouterContext.Provider,
190
- // { value: routerValue },
191
- // Component ? React.createElement(Component, { params }) : React.createElement(NotFound, null)
192
- // );
193
- // }
194
-
195
- // export function Link({ to, children, ...props }) {
196
- // const { navigate } = useRouter();
197
-
198
- // function handleClick(e) {
199
- // e.preventDefault();
200
- // navigate(to);
201
- // }
202
-
203
- // return React.createElement('a', { href: to, onClick: handleClick, ...props }, children);
204
- // }
205
-
206
- // function NotFound() {
207
- // return React.createElement(
208
- // 'div',
209
- // {
210
- // style: {
211
- // display: 'flex',
212
- // flexDirection: 'column',
213
- // alignItems: 'center',
214
- // justifyContent: 'center',
215
- // minHeight: '100vh',
216
- // fontFamily: 'system-ui'
217
- // }
218
- // },
219
- // React.createElement('h1', { style: { fontSize: '6rem', margin: 0 } }, '404'),
220
- // React.createElement('p', { style: { fontSize: '1.5rem', color: '#666' } }, 'Page not found'),
221
- // React.createElement('a', {
222
- // href: '/',
223
- // style: { color: '#10b981', textDecoration: 'none', fontSize: '1.2rem' }
224
- // }, 'Go home')
225
- // );
226
- // }
227
-
228
- // ${imports}
229
-
230
- // export const routes = [
231
- // ${routeConfigs}
232
- // ];
233
- // `;
234
-
235
- // const routerPath = join(outDir, 'router.js');
236
- // await Bun.write(routerPath, routerComponentCode);
237
- // }
238
-
239
- // async function compileDirectory(srcDir, outDir, root) {
240
- // const stats = { files: 0, skipped: 0 };
241
-
242
- // const files = readdirSync(srcDir);
243
-
244
- // for (const file of files) {
245
- // const srcPath = join(srcDir, file);
246
- // const stat = statSync(srcPath);
247
-
248
- // if (stat.isDirectory()) {
249
- // const subOutDir = join(outDir, file);
250
- // mkdirSync(subOutDir, { recursive: true });
251
- // const subStats = await compileDirectory(srcPath, subOutDir, root);
252
- // stats.files += subStats.files;
253
- // stats.skipped += subStats.skipped;
254
- // } else {
255
- // const ext = extname(file);
256
- // const relativePath = relative(join(root, 'src'), srcPath);
257
-
258
- // if (['.jsx', '.tsx', '.ts'].includes(ext)) {
259
- // await compileFile(srcPath, outDir, file, relativePath);
260
- // stats.files++;
261
- // } else if (ext === '.js') {
262
- // const outPath = join(outDir, file);
263
- // let code = await Bun.file(srcPath).text();
264
-
265
- // code = fixImports(code);
266
-
267
- // await Bun.write(outPath, code);
268
- // logger.debug(`Copied: ${relativePath}`);
269
- // stats.files++;
270
- // } else {
271
- // logger.debug(`Skipped: ${relativePath}`);
272
- // stats.skipped++;
273
- // }
274
- // }
275
- // }
276
-
277
- // return stats;
278
- // }
279
-
280
- // async function compileFile(srcPath, outDir, filename, relativePath) {
281
- // const ext = extname(filename);
282
- // const loader = ext === '.tsx' ? 'tsx' : ext === '.ts' ? 'ts' : 'jsx';
283
-
284
- // try {
285
- // let code = await Bun.file(srcPath).text();
286
-
287
- // code = fixImports(code);
288
-
289
- // const transpiler = new Bun.Transpiler({
290
- // loader,
291
- // tsconfig: {
292
- // compilerOptions: {
293
- // jsx: 'react',
294
- // jsxFactory: 'React.createElement',
295
- // jsxFragmentFactory: 'React.Fragment'
296
- // }
297
- // }
298
- // });
299
- // let compiled = await transpiler.transform(code);
300
-
301
- // if (!compiled.includes('import React') && (compiled.includes('React.createElement') || compiled.includes('React.Fragment'))) {
302
- // compiled = `import React from 'react';\n${compiled}`;
303
- // }
304
-
305
- // compiled = fixRelativeImports(compiled);
306
-
307
- // const outFilename = filename.replace(/\.(jsx|tsx|ts)$/, '.js');
308
- // const outPath = join(outDir, outFilename);
309
-
310
- // await Bun.write(outPath, compiled);
311
- // logger.debug(`Compiled: ${relativePath} → ${outFilename}`);
312
- // } catch (error) {
313
- // logger.error(`Failed to compile ${relativePath}: ${error.message}`);
314
- // throw error;
315
- // }
316
- // }
317
-
318
- // function fixImports(code) {
319
- // code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
320
-
321
- // code = code.replace(
322
- // /from\s+['"]bertui\/router['"]/g,
323
- // "from '/compiled/router.js'"
324
- // );
325
-
326
- // code = code.replace(
327
- // /from\s+['"]\.\.\/\.bertui\/compiled\/([^'"]+)['"]/g,
328
- // "from '/compiled/$1'"
329
- // );
330
-
331
- // return code;
332
- // }
333
-
334
- // function fixRelativeImports(code) {
335
- // const importRegex = /from\s+['"](\.\.[\/\\]|\.\/)((?:[^'"]+?)(?<!\.js|\.jsx|\.ts|\.tsx|\.json))['"];?/g;
336
-
337
- // code = code.replace(importRegex, (match, prefix, path) => {
338
- // if (path.endsWith('/') || /\.\w+$/.test(path)) {
339
- // return match;
340
- // }
341
- // return `from '${prefix}${path}.js';`;
342
- // });
343
-
344
- // return code;
345
- // }
346
-
347
1
  import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
348
2
  import { join, extname, relative } from 'path';
349
3
  import logger from '../logger/logger.js';
@@ -662,19 +316,16 @@ async function compileFile(srcPath, outDir, filename, relativePath) {
662
316
  }
663
317
 
664
318
  function fixImports(code) {
665
- // Remove bertui/styles imports
666
319
  code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
667
320
 
668
- // Fix bertui/router imports to use relative path to compiled router
669
321
  code = code.replace(
670
322
  /from\s+['"]bertui\/router['"]/g,
671
- "from '../.bertui/compiled/router.js'"
323
+ "from '/compiled/router.js'"
672
324
  );
673
325
 
674
- // Also handle any absolute /compiled/ paths
675
326
  code = code.replace(
676
- /from\s+['"]\/compiled\/router\.js['"]/g,
677
- "from '../.bertui/compiled/router.js'"
327
+ /from\s+['"]\.\.\/\.bertui\/compiled\/([^'"]+)['"]/g,
328
+ "from '/compiled/$1'"
678
329
  );
679
330
 
680
331
  return code;
@@ -691,4 +342,5 @@ function fixRelativeImports(code) {
691
342
  });
692
343
 
693
344
  return code;
694
- }
345
+ }
346
+
@@ -31,24 +31,7 @@ export async function startDevServer(options = {}) {
31
31
  const path = params['*'];
32
32
 
33
33
  if (path.includes('.')) {
34
- // Handle .bertui/compiled/ paths
35
- if (path.startsWith('.bertui/compiled/')) {
36
- const filePath = join(root, path);
37
- const file = Bun.file(filePath);
38
-
39
- if (await file.exists()) {
40
- const ext = extname(path);
41
- const contentType = ext === '.js' ? 'application/javascript' : getContentType(ext);
42
-
43
- return new Response(await file.text(), {
44
- headers: {
45
- 'Content-Type': contentType,
46
- 'Cache-Control': 'no-store'
47
- }
48
- });
49
- }
50
- }
51
-
34
+ // Handle compiled directory files
52
35
  if (path.startsWith('compiled/')) {
53
36
  const filePath = join(compiledDir, path.replace('compiled/', ''));
54
37
  const file = Bun.file(filePath);
@@ -225,7 +208,7 @@ function serveHTML(root, hasRouter, config) {
225
208
  <body>
226
209
  <div id="root"></div>
227
210
  <script type="module" src="/hmr-client.js"></script>
228
- <script type="module" src="/.bertui/compiled/main.js"></script>
211
+ <script type="module" src="/compiled/main.js"></script>
229
212
  </body>
230
213
  </html>`;
231
214