bertui 0.1.6 → 0.1.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.
@@ -1,273 +1,280 @@
1
- // src/server/dev-server.js
2
- import { Elysia } from 'elysia';
3
- import { watch } from 'fs';
4
- import { join, extname } from 'path';
5
- import { existsSync } from 'fs';
6
- import logger from '../logger/logger.js';
7
- import { compileProject } from '../client/compiler.js';
8
-
9
- export async function startDevServer(options = {}) {
10
- const port = parseInt(options.port) || 3000;
11
- const root = options.root || process.cwd();
12
- const compiledDir = join(root, '.bertui', 'compiled');
13
-
14
- const clients = new Set();
15
- let hasRouter = false;
16
-
17
- // Check if router exists
18
- const routerPath = join(compiledDir, 'router.js');
19
- if (existsSync(routerPath)) {
20
- hasRouter = true;
21
- logger.info('Router-based routing enabled');
22
- }
23
-
24
- const app = new Elysia()
25
- // Main HTML route - serves all pages
26
- .get('/', async () => {
27
- return serveHTML(root, hasRouter);
28
- })
29
-
30
- // Catch-all route for SPA routing
31
- .get('/*', async ({ params, set }) => {
32
- const path = params['*'];
33
-
34
- // Check if it's a file request
35
- if (path.includes('.')) {
36
- // Try to serve as static file
37
- const filePath = join(compiledDir, path);
38
- const file = Bun.file(filePath);
39
-
40
- if (await file.exists()) {
41
- const ext = extname(path);
42
- const contentType = getContentType(ext);
43
-
44
- return new Response(await file.text(), {
45
- headers: {
46
- 'Content-Type': contentType,
47
- 'Cache-Control': 'no-store'
48
- }
49
- });
50
- }
51
-
52
- set.status = 404;
53
- return 'File not found';
54
- }
55
-
56
- // For non-file routes, serve the main HTML (SPA mode)
57
- return serveHTML(root, hasRouter);
58
- })
59
-
60
- .get('/hmr-client.js', () => {
61
- const script = `
62
- const ws = new WebSocket('ws://localhost:${port}/hmr');
63
-
64
- ws.onopen = () => {
65
- console.log('%cšŸ”„ BertUI HMR connected', 'color: #10b981; font-weight: bold');
66
- };
67
-
68
- ws.onmessage = (event) => {
69
- const data = JSON.parse(event.data);
70
-
71
- if (data.type === 'reload') {
72
- console.log('%cšŸ”„ Reloading...', 'color: #f59e0b');
73
- window.location.reload();
74
- }
75
-
76
- if (data.type === 'recompiling') {
77
- console.log('%cāš™ļø Recompiling...', 'color: #3b82f6');
78
- }
79
- };
80
-
81
- ws.onerror = (error) => {
82
- console.error('%cāŒ HMR connection error', 'color: #ef4444', error);
83
- };
84
-
85
- ws.onclose = () => {
86
- console.log('%cāš ļø HMR disconnected. Refresh to reconnect.', 'color: #f59e0b');
87
- };
88
- `;
89
-
90
- return new Response(script, {
91
- headers: { 'Content-Type': 'application/javascript' }
92
- });
93
- })
94
-
95
- .ws('/hmr', {
96
- open(ws) {
97
- clients.add(ws);
98
- logger.info('Client connected to HMR');
99
- },
100
- close(ws) {
101
- clients.delete(ws);
102
- logger.info('Client disconnected from HMR');
103
- }
104
- })
105
-
106
- // Serve BertUI CSS
107
- .get('/styles/bertui.css', async ({ set }) => {
108
- const cssPath = join(import.meta.dir, '../styles/bertui.css');
109
- const file = Bun.file(cssPath);
110
-
111
- if (!await file.exists()) {
112
- set.status = 404;
113
- return 'CSS file not found';
114
- }
115
-
116
- return new Response(await file.text(), {
117
- headers: {
118
- 'Content-Type': 'text/css',
119
- 'Cache-Control': 'no-store'
120
- }
121
- });
122
- })
123
-
124
- // Serve compiled files
125
- .get('/compiled/*', async ({ params, set }) => {
126
- const filepath = join(compiledDir, params['*']);
127
- const file = Bun.file(filepath);
128
-
129
- if (!await file.exists()) {
130
- set.status = 404;
131
- return 'File not found';
132
- }
133
-
134
- const ext = extname(filepath);
135
- const contentType = getContentType(ext);
136
-
137
- return new Response(await file.text(), {
138
- headers: {
139
- 'Content-Type': contentType,
140
- 'Cache-Control': 'no-store'
141
- }
142
- });
143
- })
144
-
145
- // Serve public assets
146
- .get('/public/*', async ({ params, set }) => {
147
- const publicDir = join(root, 'public');
148
- const filepath = join(publicDir, params['*']);
149
- const file = Bun.file(filepath);
150
-
151
- if (!await file.exists()) {
152
- set.status = 404;
153
- return 'File not found';
154
- }
155
-
156
- return new Response(file);
157
- })
158
-
159
- .listen(port);
160
-
161
- if (!app.server) {
162
- logger.error('Failed to start server');
163
- process.exit(1);
164
- }
165
-
166
- logger.success(`šŸš€ Server running at http://localhost:${port}`);
167
- logger.info(`šŸ“ Serving: ${root}`);
168
-
169
- // Watch for file changes
170
- setupWatcher(root, compiledDir, clients, () => {
171
- // Check router status on recompile
172
- hasRouter = existsSync(join(compiledDir, 'router.js'));
173
- });
174
-
175
- return app;
176
- }
177
-
178
- function serveHTML(root, hasRouter) {
179
- const html = `
180
- <!DOCTYPE html>
181
- <html lang="en">
182
- <head>
183
- <meta charset="UTF-8">
184
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
185
- <title>BertUI App - Dev</title>
186
- <link rel="stylesheet" href="/styles/bertui.css">
187
- </head>
188
- <body>
189
- <div id="root"></div>
190
- <script type="module" src="/hmr-client.js"></script>
191
- ${hasRouter
192
- ? '<script type="module" src="/compiled/router.js"></script>'
193
- : ''
194
- }
195
- <script type="module" src="/compiled/main.js"></script>
196
- </body>
197
- </html>`;
198
-
199
- return new Response(html, {
200
- headers: { 'Content-Type': 'text/html' }
201
- });
202
- }
203
-
204
- function getContentType(ext) {
205
- const types = {
206
- '.js': 'application/javascript',
207
- '.css': 'text/css',
208
- '.html': 'text/html',
209
- '.json': 'application/json',
210
- '.png': 'image/png',
211
- '.jpg': 'image/jpeg',
212
- '.jpeg': 'image/jpeg',
213
- '.gif': 'image/gif',
214
- '.svg': 'image/svg+xml',
215
- '.ico': 'image/x-icon',
216
- '.woff': 'font/woff',
217
- '.woff2': 'font/woff2',
218
- '.ttf': 'font/ttf',
219
- '.eot': 'application/vnd.ms-fontobject'
220
- };
221
-
222
- return types[ext] || 'text/plain';
223
- }
224
-
225
- function setupWatcher(root, compiledDir, clients, onRecompile) {
226
- const srcDir = join(root, 'src');
227
-
228
- if (!existsSync(srcDir)) {
229
- logger.warn('src/ directory not found');
230
- return;
231
- }
232
-
233
- logger.info(`šŸ‘€ Watching: ${srcDir}`);
234
-
235
- watch(srcDir, { recursive: true }, async (eventType, filename) => {
236
- if (!filename) return;
237
-
238
- const ext = extname(filename);
239
- if (['.js', '.jsx', '.ts', '.tsx', '.css'].includes(ext)) {
240
- logger.info(`šŸ“ File changed: ${filename}`);
241
-
242
- // Notify clients that recompilation is starting
243
- for (const client of clients) {
244
- try {
245
- client.send(JSON.stringify({ type: 'recompiling' }));
246
- } catch (e) {
247
- clients.delete(client);
248
- }
249
- }
250
-
251
- // Recompile the project
252
- try {
253
- await compileProject(root);
254
-
255
- // Call callback to update router status
256
- if (onRecompile) {
257
- onRecompile();
258
- }
259
-
260
- // Notify clients to reload
261
- for (const client of clients) {
262
- try {
263
- client.send(JSON.stringify({ type: 'reload', file: filename }));
264
- } catch (e) {
265
- clients.delete(client);
266
- }
267
- }
268
- } catch (error) {
269
- logger.error(`Recompilation failed: ${error.message}`);
270
- }
271
- }
272
- });
1
+ // src/server/dev-server.js
2
+ import { Elysia } from 'elysia';
3
+ import { watch } from 'fs';
4
+ import { join, extname } from 'path';
5
+ import { existsSync } from 'fs';
6
+ import logger from '../logger/logger.js';
7
+ import { compileProject } from '../client/compiler.js';
8
+
9
+ export async function startDevServer(options = {}) {
10
+ const port = parseInt(options.port) || 3000;
11
+ const root = options.root || process.cwd();
12
+ const compiledDir = join(root, '.bertui', 'compiled');
13
+
14
+ const clients = new Set();
15
+ let hasRouter = false;
16
+
17
+ // Check if router exists
18
+ const routerPath = join(compiledDir, 'router.js');
19
+ if (existsSync(routerPath)) {
20
+ hasRouter = true;
21
+ logger.info('Router-based routing enabled');
22
+ }
23
+
24
+ const app = new Elysia()
25
+ // Main HTML route - serves all pages
26
+ .get('/', async () => {
27
+ return serveHTML(root, hasRouter);
28
+ })
29
+
30
+ // Catch-all route for SPA routing
31
+ .get('/*', async ({ params, set }) => {
32
+ const path = params['*'];
33
+
34
+ // Check if it's a file request
35
+ if (path.includes('.')) {
36
+ // Try to serve compiled files
37
+ if (path.startsWith('compiled/')) {
38
+ const filePath = join(compiledDir, path.replace('compiled/', ''));
39
+ const file = Bun.file(filePath);
40
+
41
+ if (await file.exists()) {
42
+ const ext = extname(path);
43
+ // CRITICAL FIX: Serve .js files with correct MIME type
44
+ const contentType = ext === '.js' || ext === '.jsx'
45
+ ? 'application/javascript'
46
+ : getContentType(ext);
47
+
48
+ return new Response(await file.text(), {
49
+ headers: {
50
+ 'Content-Type': contentType,
51
+ 'Cache-Control': 'no-store'
52
+ }
53
+ });
54
+ }
55
+ }
56
+
57
+ set.status = 404;
58
+ return 'File not found';
59
+ }
60
+
61
+ // For non-file routes, serve the main HTML (SPA mode)
62
+ return serveHTML(root, hasRouter);
63
+ })
64
+
65
+ .get('/hmr-client.js', () => {
66
+ const script = `
67
+ const ws = new WebSocket('ws://localhost:${port}/hmr');
68
+
69
+ ws.onopen = () => {
70
+ console.log('%cšŸ”„ BertUI HMR connected', 'color: #10b981; font-weight: bold');
71
+ };
72
+
73
+ ws.onmessage = (event) => {
74
+ const data = JSON.parse(event.data);
75
+
76
+ if (data.type === 'reload') {
77
+ console.log('%cšŸ”„ Reloading...', 'color: #f59e0b');
78
+ window.location.reload();
79
+ }
80
+
81
+ if (data.type === 'recompiling') {
82
+ console.log('%cāš™ļø Recompiling...', 'color: #3b82f6');
83
+ }
84
+ };
85
+
86
+ ws.onerror = (error) => {
87
+ console.error('%cāŒ HMR connection error', 'color: #ef4444', error);
88
+ };
89
+
90
+ ws.onclose = () => {
91
+ console.log('%cāš ļø HMR disconnected. Refresh to reconnect.', 'color: #f59e0b');
92
+ };
93
+ `;
94
+
95
+ return new Response(script, {
96
+ headers: { 'Content-Type': 'application/javascript' }
97
+ });
98
+ })
99
+
100
+ .ws('/hmr', {
101
+ open(ws) {
102
+ clients.add(ws);
103
+ logger.info('Client connected to HMR');
104
+ },
105
+ close(ws) {
106
+ clients.delete(ws);
107
+ logger.info('Client disconnected from HMR');
108
+ }
109
+ })
110
+
111
+ // Serve BertUI CSS
112
+ .get('/styles/bertui.css', async ({ set }) => {
113
+ const cssPath = join(import.meta.dir, '../styles/bertui.css');
114
+ const file = Bun.file(cssPath);
115
+
116
+ if (!await file.exists()) {
117
+ set.status = 404;
118
+ return 'CSS file not found';
119
+ }
120
+
121
+ return new Response(await file.text(), {
122
+ headers: {
123
+ 'Content-Type': 'text/css',
124
+ 'Cache-Control': 'no-store'
125
+ }
126
+ });
127
+ })
128
+
129
+ // Serve compiled files with correct MIME types
130
+ .get('/compiled/*', async ({ params, set }) => {
131
+ const filepath = join(compiledDir, params['*']);
132
+ const file = Bun.file(filepath);
133
+
134
+ if (!await file.exists()) {
135
+ set.status = 404;
136
+ return 'File not found';
137
+ }
138
+
139
+ const ext = extname(filepath);
140
+ // CRITICAL FIX: Always serve .js files with JavaScript MIME type
141
+ const contentType = ext === '.js'
142
+ ? 'application/javascript'
143
+ : getContentType(ext);
144
+
145
+ return new Response(await file.text(), {
146
+ headers: {
147
+ 'Content-Type': contentType,
148
+ 'Cache-Control': 'no-store'
149
+ }
150
+ });
151
+ })
152
+
153
+ // Serve public assets
154
+ .get('/public/*', async ({ params, set }) => {
155
+ const publicDir = join(root, 'public');
156
+ const filepath = join(publicDir, params['*']);
157
+ const file = Bun.file(filepath);
158
+
159
+ if (!await file.exists()) {
160
+ set.status = 404;
161
+ return 'File not found';
162
+ }
163
+
164
+ return new Response(file);
165
+ })
166
+
167
+ .listen(port);
168
+
169
+ if (!app.server) {
170
+ logger.error('Failed to start server');
171
+ process.exit(1);
172
+ }
173
+
174
+ logger.success(`šŸš€ Server running at http://localhost:${port}`);
175
+ logger.info(`šŸ“ Serving: ${root}`);
176
+
177
+ // Watch for file changes
178
+ setupWatcher(root, compiledDir, clients, () => {
179
+ hasRouter = existsSync(join(compiledDir, 'router.js'));
180
+ });
181
+
182
+ return app;
183
+ }
184
+
185
+ function serveHTML(root, hasRouter) {
186
+ const html = `
187
+ <!DOCTYPE html>
188
+ <html lang="en">
189
+ <head>
190
+ <meta charset="UTF-8">
191
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
192
+ <title>BertUI App - Dev</title>
193
+ <link rel="stylesheet" href="/styles/bertui.css">
194
+ </head>
195
+ <body>
196
+ <div id="root"></div>
197
+ <script type="module" src="/hmr-client.js"></script>
198
+ ${hasRouter
199
+ ? '<script type="module" src="/compiled/router.js"></script>'
200
+ : ''
201
+ }
202
+ <script type="module" src="/compiled/main.js"></script>
203
+ </body>
204
+ </html>`;
205
+
206
+ return new Response(html, {
207
+ headers: { 'Content-Type': 'text/html' }
208
+ });
209
+ }
210
+
211
+ function getContentType(ext) {
212
+ const types = {
213
+ '.js': 'application/javascript',
214
+ '.jsx': 'application/javascript',
215
+ '.css': 'text/css',
216
+ '.html': 'text/html',
217
+ '.json': 'application/json',
218
+ '.png': 'image/png',
219
+ '.jpg': 'image/jpeg',
220
+ '.jpeg': 'image/jpeg',
221
+ '.gif': 'image/gif',
222
+ '.svg': 'image/svg+xml',
223
+ '.ico': 'image/x-icon',
224
+ '.woff': 'font/woff',
225
+ '.woff2': 'font/woff2',
226
+ '.ttf': 'font/ttf',
227
+ '.eot': 'application/vnd.ms-fontobject'
228
+ };
229
+
230
+ return types[ext] || 'text/plain';
231
+ }
232
+
233
+ function setupWatcher(root, compiledDir, clients, onRecompile) {
234
+ const srcDir = join(root, 'src');
235
+
236
+ if (!existsSync(srcDir)) {
237
+ logger.warn('src/ directory not found');
238
+ return;
239
+ }
240
+
241
+ logger.info(`šŸ‘€ Watching: ${srcDir}`);
242
+
243
+ watch(srcDir, { recursive: true }, async (eventType, filename) => {
244
+ if (!filename) return;
245
+
246
+ const ext = extname(filename);
247
+ if (['.js', '.jsx', '.ts', '.tsx', '.css'].includes(ext)) {
248
+ logger.info(`šŸ“ File changed: ${filename}`);
249
+
250
+ // Notify clients
251
+ for (const client of clients) {
252
+ try {
253
+ client.send(JSON.stringify({ type: 'recompiling' }));
254
+ } catch (e) {
255
+ clients.delete(client);
256
+ }
257
+ }
258
+
259
+ // Recompile
260
+ try {
261
+ await compileProject(root);
262
+
263
+ if (onRecompile) {
264
+ onRecompile();
265
+ }
266
+
267
+ // Notify reload
268
+ for (const client of clients) {
269
+ try {
270
+ client.send(JSON.stringify({ type: 'reload', file: filename }));
271
+ } catch (e) {
272
+ clients.delete(client);
273
+ }
274
+ }
275
+ } catch (error) {
276
+ logger.error(`Recompilation failed: ${error.message}`);
277
+ }
278
+ }
279
+ });
273
280
  }