bertui 0.2.4 → 0.2.6
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/index.js +1 -1
- package/package.json +1 -1
- package/src/client/compiler.js +352 -3
- package/src/server/dev-server.js +70 -13
package/index.js
CHANGED
package/package.json
CHANGED
package/src/client/compiler.js
CHANGED
|
@@ -1,3 +1,349 @@
|
|
|
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
|
+
|
|
1
347
|
import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
2
348
|
import { join, extname, relative } from 'path';
|
|
3
349
|
import logger from '../logger/logger.js';
|
|
@@ -316,16 +662,19 @@ async function compileFile(srcPath, outDir, filename, relativePath) {
|
|
|
316
662
|
}
|
|
317
663
|
|
|
318
664
|
function fixImports(code) {
|
|
665
|
+
// Remove bertui/styles imports
|
|
319
666
|
code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
|
|
320
667
|
|
|
668
|
+
// Fix bertui/router imports to use relative path to compiled router
|
|
321
669
|
code = code.replace(
|
|
322
670
|
/from\s+['"]bertui\/router['"]/g,
|
|
323
|
-
"from '/compiled/router.js'"
|
|
671
|
+
"from '../.bertui/compiled/router.js'"
|
|
324
672
|
);
|
|
325
673
|
|
|
674
|
+
// Also handle any absolute /compiled/ paths
|
|
326
675
|
code = code.replace(
|
|
327
|
-
/from\s+['"]
|
|
328
|
-
"from '/compiled
|
|
676
|
+
/from\s+['"]\/compiled\/router\.js['"]/g,
|
|
677
|
+
"from '../.bertui/compiled/router.js'"
|
|
329
678
|
);
|
|
330
679
|
|
|
331
680
|
return code;
|
package/src/server/dev-server.js
CHANGED
|
@@ -4,12 +4,15 @@ import { join, extname } from 'path';
|
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
5
|
import logger from '../logger/logger.js';
|
|
6
6
|
import { compileProject } from '../client/compiler.js';
|
|
7
|
+
import { loadConfig } from '../config/loadConfig.js';
|
|
7
8
|
|
|
8
9
|
export async function startDevServer(options = {}) {
|
|
9
10
|
const port = parseInt(options.port) || 3000;
|
|
10
11
|
const root = options.root || process.cwd();
|
|
11
12
|
const compiledDir = join(root, '.bertui', 'compiled');
|
|
12
13
|
|
|
14
|
+
const config = await loadConfig(root);
|
|
15
|
+
|
|
13
16
|
const clients = new Set();
|
|
14
17
|
let hasRouter = false;
|
|
15
18
|
|
|
@@ -21,13 +24,31 @@ export async function startDevServer(options = {}) {
|
|
|
21
24
|
|
|
22
25
|
const app = new Elysia()
|
|
23
26
|
.get('/', async () => {
|
|
24
|
-
return serveHTML(root, hasRouter);
|
|
27
|
+
return serveHTML(root, hasRouter, config);
|
|
25
28
|
})
|
|
26
29
|
|
|
27
30
|
.get('/*', async ({ params, set }) => {
|
|
28
31
|
const path = params['*'];
|
|
29
32
|
|
|
30
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
|
+
|
|
31
52
|
if (path.startsWith('compiled/')) {
|
|
32
53
|
const filePath = join(compiledDir, path.replace('compiled/', ''));
|
|
33
54
|
const file = Bun.file(filePath);
|
|
@@ -45,11 +66,21 @@ export async function startDevServer(options = {}) {
|
|
|
45
66
|
}
|
|
46
67
|
}
|
|
47
68
|
|
|
69
|
+
if (path.startsWith('public/')) {
|
|
70
|
+
const publicDir = join(root, 'public');
|
|
71
|
+
const filepath = join(publicDir, path.replace('public/', ''));
|
|
72
|
+
const file = Bun.file(filepath);
|
|
73
|
+
|
|
74
|
+
if (await file.exists()) {
|
|
75
|
+
return new Response(file);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
48
79
|
set.status = 404;
|
|
49
80
|
return 'File not found';
|
|
50
81
|
}
|
|
51
82
|
|
|
52
|
-
return serveHTML(root, hasRouter);
|
|
83
|
+
return serveHTML(root, hasRouter, config);
|
|
53
84
|
})
|
|
54
85
|
|
|
55
86
|
.get('/hmr-client.js', () => {
|
|
@@ -141,29 +172,41 @@ ws.onclose = () => {
|
|
|
141
172
|
logger.success(`🚀 Server running at http://localhost:${port}`);
|
|
142
173
|
logger.info(`📁 Serving: ${root}`);
|
|
143
174
|
|
|
144
|
-
setupWatcher(root, compiledDir, clients, () => {
|
|
175
|
+
setupWatcher(root, compiledDir, clients, async () => {
|
|
145
176
|
hasRouter = existsSync(join(compiledDir, 'router.js'));
|
|
146
177
|
});
|
|
147
178
|
|
|
148
179
|
return app;
|
|
149
180
|
}
|
|
150
181
|
|
|
151
|
-
function serveHTML(root, hasRouter) {
|
|
182
|
+
function serveHTML(root, hasRouter, config) {
|
|
183
|
+
const meta = config.meta || {};
|
|
184
|
+
|
|
152
185
|
const html = `
|
|
153
186
|
<!DOCTYPE html>
|
|
154
|
-
<html lang="en">
|
|
187
|
+
<html lang="${meta.lang || 'en'}">
|
|
155
188
|
<head>
|
|
156
189
|
<meta charset="UTF-8">
|
|
157
190
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
158
|
-
<title
|
|
191
|
+
<title>${meta.title || 'BertUI App'}</title>
|
|
192
|
+
|
|
193
|
+
${meta.description ? `<meta name="description" content="${meta.description}">` : ''}
|
|
194
|
+
${meta.keywords ? `<meta name="keywords" content="${meta.keywords}">` : ''}
|
|
195
|
+
${meta.author ? `<meta name="author" content="${meta.author}">` : ''}
|
|
196
|
+
${meta.themeColor ? `<meta name="theme-color" content="${meta.themeColor}">` : ''}
|
|
197
|
+
|
|
198
|
+
${meta.ogTitle ? `<meta property="og:title" content="${meta.ogTitle || meta.title}">` : ''}
|
|
199
|
+
${meta.ogDescription ? `<meta property="og:description" content="${meta.ogDescription || meta.description}">` : ''}
|
|
200
|
+
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
201
|
+
|
|
202
|
+
<link rel="icon" type="image/svg+xml" href="/public/favicon.svg">
|
|
159
203
|
|
|
160
204
|
<script type="importmap">
|
|
161
205
|
{
|
|
162
206
|
"imports": {
|
|
163
207
|
"react": "https://esm.sh/react@18.2.0",
|
|
164
208
|
"react-dom": "https://esm.sh/react-dom@18.2.0",
|
|
165
|
-
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client"
|
|
166
|
-
"bertui/router": "/compiled/router.js"
|
|
209
|
+
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client"
|
|
167
210
|
}
|
|
168
211
|
}
|
|
169
212
|
</script>
|
|
@@ -182,10 +225,7 @@ function serveHTML(root, hasRouter) {
|
|
|
182
225
|
<body>
|
|
183
226
|
<div id="root"></div>
|
|
184
227
|
<script type="module" src="/hmr-client.js"></script>
|
|
185
|
-
|
|
186
|
-
? '<script type="module" src="/compiled/main.js"></script>'
|
|
187
|
-
: '<script type="module" src="/compiled/main.js"></script>'
|
|
188
|
-
}
|
|
228
|
+
<script type="module" src="/.bertui/compiled/main.js"></script>
|
|
189
229
|
</body>
|
|
190
230
|
</html>`;
|
|
191
231
|
|
|
@@ -214,6 +254,7 @@ function getContentType(ext) {
|
|
|
214
254
|
|
|
215
255
|
function setupWatcher(root, compiledDir, clients, onRecompile) {
|
|
216
256
|
const srcDir = join(root, 'src');
|
|
257
|
+
const configPath = join(root, 'bertui.config.js');
|
|
217
258
|
|
|
218
259
|
if (!existsSync(srcDir)) {
|
|
219
260
|
logger.warn('src/ directory not found');
|
|
@@ -241,7 +282,7 @@ function setupWatcher(root, compiledDir, clients, onRecompile) {
|
|
|
241
282
|
await compileProject(root);
|
|
242
283
|
|
|
243
284
|
if (onRecompile) {
|
|
244
|
-
onRecompile();
|
|
285
|
+
await onRecompile();
|
|
245
286
|
}
|
|
246
287
|
|
|
247
288
|
for (const client of clients) {
|
|
@@ -256,4 +297,20 @@ function setupWatcher(root, compiledDir, clients, onRecompile) {
|
|
|
256
297
|
}
|
|
257
298
|
}
|
|
258
299
|
});
|
|
300
|
+
|
|
301
|
+
if (existsSync(configPath)) {
|
|
302
|
+
watch(configPath, async (eventType) => {
|
|
303
|
+
if (eventType === 'change') {
|
|
304
|
+
logger.info('📝 Config changed, reloading...');
|
|
305
|
+
|
|
306
|
+
for (const client of clients) {
|
|
307
|
+
try {
|
|
308
|
+
client.send(JSON.stringify({ type: 'reload', file: 'bertui.config.js' }));
|
|
309
|
+
} catch (e) {
|
|
310
|
+
clients.delete(client);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
259
316
|
}
|