bertui 0.1.6 ā 0.1.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/LICENSE +20 -20
- package/README.md +1 -1
- package/bin/bertui.js +7 -7
- package/index.js +35 -35
- package/package.json +5 -5
- package/src/build/css-builder.js +83 -83
- package/src/build.js +122 -122
- package/src/cli.js +65 -65
- package/src/client/compiler.js +218 -225
- package/src/config/defaultConfig.js +15 -15
- package/src/config/loadConfig.js +32 -32
- package/src/dev.js +22 -22
- package/src/logger/logger.js +17 -17
- package/src/logger/notes.md +19 -19
- package/src/router/{router.js ā Router.js} +128 -128
- package/src/server/dev-server.js +262 -272
- package/src/styles/bertui.css +209 -209
package/src/server/dev-server.js
CHANGED
|
@@ -1,273 +1,263 @@
|
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
ws.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
ws.
|
|
82
|
-
console.
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
clients.
|
|
98
|
-
logger.info('Client
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
'.
|
|
207
|
-
'.
|
|
208
|
-
'.
|
|
209
|
-
'.
|
|
210
|
-
'.
|
|
211
|
-
'.
|
|
212
|
-
'.
|
|
213
|
-
'.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
+
const routerPath = join(compiledDir, 'router.js');
|
|
18
|
+
if (existsSync(routerPath)) {
|
|
19
|
+
hasRouter = true;
|
|
20
|
+
logger.info('Router-based routing enabled');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const app = new Elysia()
|
|
24
|
+
.get('/', async () => {
|
|
25
|
+
return serveHTML(root, hasRouter);
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
.get('/*', async ({ params, set }) => {
|
|
29
|
+
const path = params['*'];
|
|
30
|
+
|
|
31
|
+
if (path.includes('.')) {
|
|
32
|
+
if (path.startsWith('compiled/')) {
|
|
33
|
+
const filePath = join(compiledDir, path.replace('compiled/', ''));
|
|
34
|
+
const file = Bun.file(filePath);
|
|
35
|
+
|
|
36
|
+
if (await file.exists()) {
|
|
37
|
+
const ext = extname(path);
|
|
38
|
+
const contentType = ext === '.js' ? 'application/javascript' : getContentType(ext);
|
|
39
|
+
|
|
40
|
+
return new Response(await file.text(), {
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': contentType,
|
|
43
|
+
'Cache-Control': 'no-store'
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set.status = 404;
|
|
50
|
+
return 'File not found';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return serveHTML(root, hasRouter);
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
.get('/hmr-client.js', () => {
|
|
57
|
+
const script = `
|
|
58
|
+
const ws = new WebSocket('ws://localhost:${port}/hmr');
|
|
59
|
+
|
|
60
|
+
ws.onopen = () => {
|
|
61
|
+
console.log('%cš„ BertUI HMR connected', 'color: #10b981; font-weight: bold');
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
ws.onmessage = (event) => {
|
|
65
|
+
const data = JSON.parse(event.data);
|
|
66
|
+
|
|
67
|
+
if (data.type === 'reload') {
|
|
68
|
+
console.log('%cš Reloading...', 'color: #f59e0b');
|
|
69
|
+
window.location.reload();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (data.type === 'recompiling') {
|
|
73
|
+
console.log('%cāļø Recompiling...', 'color: #3b82f6');
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
ws.onerror = (error) => {
|
|
78
|
+
console.error('%cā HMR connection error', 'color: #ef4444', error);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
ws.onclose = () => {
|
|
82
|
+
console.log('%cā ļø HMR disconnected. Refresh to reconnect.', 'color: #f59e0b');
|
|
83
|
+
};
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
return new Response(script, {
|
|
87
|
+
headers: { 'Content-Type': 'application/javascript' }
|
|
88
|
+
});
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
.ws('/hmr', {
|
|
92
|
+
open(ws) {
|
|
93
|
+
clients.add(ws);
|
|
94
|
+
logger.info('Client connected to HMR');
|
|
95
|
+
},
|
|
96
|
+
close(ws) {
|
|
97
|
+
clients.delete(ws);
|
|
98
|
+
logger.info('Client disconnected from HMR');
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
.get('/compiled/*', async ({ params, set }) => {
|
|
103
|
+
const filepath = join(compiledDir, params['*']);
|
|
104
|
+
const file = Bun.file(filepath);
|
|
105
|
+
|
|
106
|
+
if (!await file.exists()) {
|
|
107
|
+
set.status = 404;
|
|
108
|
+
return 'File not found';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const ext = extname(filepath);
|
|
112
|
+
const contentType = ext === '.js' ? 'application/javascript' : getContentType(ext);
|
|
113
|
+
|
|
114
|
+
return new Response(await file.text(), {
|
|
115
|
+
headers: {
|
|
116
|
+
'Content-Type': contentType,
|
|
117
|
+
'Cache-Control': 'no-store'
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
.get('/public/*', async ({ params, set }) => {
|
|
123
|
+
const publicDir = join(root, 'public');
|
|
124
|
+
const filepath = join(publicDir, params['*']);
|
|
125
|
+
const file = Bun.file(filepath);
|
|
126
|
+
|
|
127
|
+
if (!await file.exists()) {
|
|
128
|
+
set.status = 404;
|
|
129
|
+
return 'File not found';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return new Response(file);
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
.listen(port);
|
|
136
|
+
|
|
137
|
+
if (!app.server) {
|
|
138
|
+
logger.error('Failed to start server');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
logger.success(`š Server running at http://localhost:${port}`);
|
|
143
|
+
logger.info(`š Serving: ${root}`);
|
|
144
|
+
|
|
145
|
+
setupWatcher(root, compiledDir, clients, () => {
|
|
146
|
+
hasRouter = existsSync(join(compiledDir, 'router.js'));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return app;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function serveHTML(root, hasRouter) {
|
|
153
|
+
const html = `
|
|
154
|
+
<!DOCTYPE html>
|
|
155
|
+
<html lang="en">
|
|
156
|
+
<head>
|
|
157
|
+
<meta charset="UTF-8">
|
|
158
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
159
|
+
<title>BertUI App - Dev</title>
|
|
160
|
+
|
|
161
|
+
<!-- Import Map for React and dependencies -->
|
|
162
|
+
<script type="importmap">
|
|
163
|
+
{
|
|
164
|
+
"imports": {
|
|
165
|
+
"react": "https://esm.sh/react@18.2.0",
|
|
166
|
+
"react-dom": "https://esm.sh/react-dom@18.2.0",
|
|
167
|
+
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
|
|
168
|
+
"bertui/router": "/compiled/router.js"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
</script>
|
|
172
|
+
|
|
173
|
+
<style>
|
|
174
|
+
/* Inline basic styles since we're skipping CSS for now */
|
|
175
|
+
* {
|
|
176
|
+
margin: 0;
|
|
177
|
+
padding: 0;
|
|
178
|
+
box-sizing: border-box;
|
|
179
|
+
}
|
|
180
|
+
body {
|
|
181
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
182
|
+
}
|
|
183
|
+
</style>
|
|
184
|
+
</head>
|
|
185
|
+
<body>
|
|
186
|
+
<div id="root"></div>
|
|
187
|
+
<script type="module" src="/hmr-client.js"></script>
|
|
188
|
+
${hasRouter
|
|
189
|
+
? '<script type="module" src="/compiled/router.js"></script>'
|
|
190
|
+
: ''
|
|
191
|
+
}
|
|
192
|
+
<script type="module" src="/compiled/main.js"></script>
|
|
193
|
+
</body>
|
|
194
|
+
</html>`;
|
|
195
|
+
|
|
196
|
+
return new Response(html, {
|
|
197
|
+
headers: { 'Content-Type': 'text/html' }
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getContentType(ext) {
|
|
202
|
+
const types = {
|
|
203
|
+
'.js': 'application/javascript',
|
|
204
|
+
'.jsx': 'application/javascript',
|
|
205
|
+
'.css': 'text/css',
|
|
206
|
+
'.html': 'text/html',
|
|
207
|
+
'.json': 'application/json',
|
|
208
|
+
'.png': 'image/png',
|
|
209
|
+
'.jpg': 'image/jpeg',
|
|
210
|
+
'.jpeg': 'image/jpeg',
|
|
211
|
+
'.gif': 'image/gif',
|
|
212
|
+
'.svg': 'image/svg+xml',
|
|
213
|
+
'.ico': 'image/x-icon'
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return types[ext] || 'text/plain';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function setupWatcher(root, compiledDir, clients, onRecompile) {
|
|
220
|
+
const srcDir = join(root, 'src');
|
|
221
|
+
|
|
222
|
+
if (!existsSync(srcDir)) {
|
|
223
|
+
logger.warn('src/ directory not found');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
logger.info(`š Watching: ${srcDir}`);
|
|
228
|
+
|
|
229
|
+
watch(srcDir, { recursive: true }, async (eventType, filename) => {
|
|
230
|
+
if (!filename) return;
|
|
231
|
+
|
|
232
|
+
const ext = extname(filename);
|
|
233
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.css'].includes(ext)) {
|
|
234
|
+
logger.info(`š File changed: ${filename}`);
|
|
235
|
+
|
|
236
|
+
for (const client of clients) {
|
|
237
|
+
try {
|
|
238
|
+
client.send(JSON.stringify({ type: 'recompiling' }));
|
|
239
|
+
} catch (e) {
|
|
240
|
+
clients.delete(client);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
await compileProject(root);
|
|
246
|
+
|
|
247
|
+
if (onRecompile) {
|
|
248
|
+
onRecompile();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const client of clients) {
|
|
252
|
+
try {
|
|
253
|
+
client.send(JSON.stringify({ type: 'reload', file: filename }));
|
|
254
|
+
} catch (e) {
|
|
255
|
+
clients.delete(client);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} catch (error) {
|
|
259
|
+
logger.error(`Recompilation failed: ${error.message}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
273
263
|
}
|