bertui 0.2.5 → 0.2.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.
- package/package.json +1 -1
- package/src/client/compiler.js +375 -13
- package/src/server/dev-server.js +2 -2
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';
|
|
@@ -256,13 +602,13 @@ async function compileDirectory(srcDir, outDir, root) {
|
|
|
256
602
|
const relativePath = relative(join(root, 'src'), srcPath);
|
|
257
603
|
|
|
258
604
|
if (['.jsx', '.tsx', '.ts'].includes(ext)) {
|
|
259
|
-
await compileFile(srcPath, outDir, file, relativePath);
|
|
605
|
+
await compileFile(srcPath, outDir, file, relativePath, root);
|
|
260
606
|
stats.files++;
|
|
261
607
|
} else if (ext === '.js') {
|
|
262
608
|
const outPath = join(outDir, file);
|
|
263
609
|
let code = await Bun.file(srcPath).text();
|
|
264
610
|
|
|
265
|
-
code = fixImports(code);
|
|
611
|
+
code = fixImports(code, srcPath, root);
|
|
266
612
|
|
|
267
613
|
await Bun.write(outPath, code);
|
|
268
614
|
logger.debug(`Copied: ${relativePath}`);
|
|
@@ -277,14 +623,14 @@ async function compileDirectory(srcDir, outDir, root) {
|
|
|
277
623
|
return stats;
|
|
278
624
|
}
|
|
279
625
|
|
|
280
|
-
async function compileFile(srcPath, outDir, filename, relativePath) {
|
|
626
|
+
async function compileFile(srcPath, outDir, filename, relativePath, root) {
|
|
281
627
|
const ext = extname(filename);
|
|
282
628
|
const loader = ext === '.tsx' ? 'tsx' : ext === '.ts' ? 'ts' : 'jsx';
|
|
283
629
|
|
|
284
630
|
try {
|
|
285
631
|
let code = await Bun.file(srcPath).text();
|
|
286
632
|
|
|
287
|
-
code = fixImports(code);
|
|
633
|
+
code = fixImports(code, srcPath, root);
|
|
288
634
|
|
|
289
635
|
const transpiler = new Bun.Transpiler({
|
|
290
636
|
loader,
|
|
@@ -315,18 +661,34 @@ async function compileFile(srcPath, outDir, filename, relativePath) {
|
|
|
315
661
|
}
|
|
316
662
|
}
|
|
317
663
|
|
|
318
|
-
function fixImports(code) {
|
|
664
|
+
function fixImports(code, srcPath, root) {
|
|
665
|
+
// Remove bertui/styles imports
|
|
319
666
|
code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
|
|
320
667
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
"from '/compiled/router.js'"
|
|
324
|
-
);
|
|
668
|
+
// Check if this is main.jsx - it will be in src/main.jsx
|
|
669
|
+
const isMainFile = srcPath.includes('main.jsx') || srcPath.includes('main.js');
|
|
325
670
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
671
|
+
if (isMainFile) {
|
|
672
|
+
// For main.jsx: router.js is in the same compiled directory
|
|
673
|
+
code = code.replace(
|
|
674
|
+
/from\s+['"]bertui\/router['"]/g,
|
|
675
|
+
"from './router.js'"
|
|
676
|
+
);
|
|
677
|
+
code = code.replace(
|
|
678
|
+
/from\s+['"]\/compiled\/router\.js['"]/g,
|
|
679
|
+
"from './router.js'"
|
|
680
|
+
);
|
|
681
|
+
code = code.replace(
|
|
682
|
+
/from\s+['"]\.\.\/\.bertui\/compiled\/router\.js['"]/g,
|
|
683
|
+
"from './router.js'"
|
|
684
|
+
);
|
|
685
|
+
} else {
|
|
686
|
+
// For page components: router.js is up one level from pages/
|
|
687
|
+
code = code.replace(
|
|
688
|
+
/from\s+['"]bertui\/router['"]/g,
|
|
689
|
+
"from '../router.js'"
|
|
690
|
+
);
|
|
691
|
+
}
|
|
330
692
|
|
|
331
693
|
return code;
|
|
332
694
|
}
|
package/src/server/dev-server.js
CHANGED
|
@@ -31,6 +31,7 @@ export async function startDevServer(options = {}) {
|
|
|
31
31
|
const path = params['*'];
|
|
32
32
|
|
|
33
33
|
if (path.includes('.')) {
|
|
34
|
+
// Handle compiled directory files
|
|
34
35
|
if (path.startsWith('compiled/')) {
|
|
35
36
|
const filePath = join(compiledDir, path.replace('compiled/', ''));
|
|
36
37
|
const file = Bun.file(filePath);
|
|
@@ -188,8 +189,7 @@ function serveHTML(root, hasRouter, config) {
|
|
|
188
189
|
"imports": {
|
|
189
190
|
"react": "https://esm.sh/react@18.2.0",
|
|
190
191
|
"react-dom": "https://esm.sh/react-dom@18.2.0",
|
|
191
|
-
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client"
|
|
192
|
-
"bertui/router": "/compiled/router.js"
|
|
192
|
+
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client"
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
</script>
|