bertui 0.1.4 → 0.1.5
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 +11 -10
- package/package.json +15 -15
- package/src/build/css-builder.js +84 -0
- package/src/build.js +50 -21
- package/src/client/compiler.js +31 -32
- package/src/config/loadConfig.js +5 -4
- package/src/server/dev-server.js +49 -103
package/index.js
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
// index.js
|
|
1
|
+
// index.js
|
|
2
2
|
import logger from "./src/logger/logger.js";
|
|
3
3
|
import { defaultConfig } from "./src/config/defaultConfig.js";
|
|
4
|
+
import { loadConfig } from "./src/config/loadConfig.js";
|
|
4
5
|
import { startDev } from "./src/dev.js";
|
|
5
6
|
import { buildProduction } from "./src/build.js";
|
|
6
7
|
import { compileProject } from "./src/client/compiler.js";
|
|
8
|
+
import { buildCSS, copyCSS } from "./src/build/css-builder.js";
|
|
7
9
|
import { program } from "./src/cli.js";
|
|
8
10
|
|
|
9
|
-
//
|
|
10
|
-
// Users import these from 'bertui/router'
|
|
11
|
-
// export { Link, navigate, Router } from './src/router/router.js';
|
|
12
|
-
// reason commnedt out is In JavaScript/TypeScript modules, if you define functions/components inside a file, you must explicitly use the export keyword for them to be available to other files that import them.
|
|
13
|
-
|
|
14
|
-
// Your router.js file contains a function, generateRouterCode(routes), which internally defines the Link, Maps, and Router components/functions as strings within a template
|
|
15
|
-
|
|
16
|
-
// Named exports for CLI and build tools
|
|
11
|
+
// Named exports
|
|
17
12
|
export {
|
|
18
13
|
logger,
|
|
19
14
|
defaultConfig,
|
|
15
|
+
loadConfig,
|
|
20
16
|
startDev,
|
|
21
17
|
buildProduction,
|
|
22
18
|
compileProject,
|
|
19
|
+
buildCSS,
|
|
20
|
+
copyCSS,
|
|
23
21
|
program
|
|
24
22
|
};
|
|
25
23
|
|
|
@@ -27,9 +25,12 @@ export {
|
|
|
27
25
|
export default {
|
|
28
26
|
logger,
|
|
29
27
|
defaultConfig,
|
|
28
|
+
loadConfig,
|
|
30
29
|
startDev,
|
|
31
30
|
buildProduction,
|
|
32
31
|
compileProject,
|
|
32
|
+
buildCSS,
|
|
33
|
+
copyCSS,
|
|
33
34
|
program,
|
|
34
|
-
version: "0.1.
|
|
35
|
+
version: "0.1.5"
|
|
35
36
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bertui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Lightning-fast React dev server powered by Bun and Elysia",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -8,22 +8,16 @@
|
|
|
8
8
|
"bertui": "./bin/bertui.js"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
|
-
".":
|
|
12
|
-
"import": "./index.js",
|
|
13
|
-
"require": "./index.js"
|
|
14
|
-
},
|
|
11
|
+
".": "./index.js",
|
|
15
12
|
"./styles": "./src/styles/bertui.css",
|
|
16
|
-
"./logger": "./src/logger/logger.js"
|
|
17
|
-
"./router": {
|
|
18
|
-
"import": "./src/router/client-exports.js",
|
|
19
|
-
"require": "./src/router/client-exports.js"
|
|
20
|
-
}
|
|
13
|
+
"./logger": "./src/logger/logger.js"
|
|
21
14
|
},
|
|
22
15
|
"files": [
|
|
23
16
|
"bin",
|
|
24
17
|
"src",
|
|
25
18
|
"index.js",
|
|
26
|
-
"README.md"
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
27
21
|
],
|
|
28
22
|
"scripts": {
|
|
29
23
|
"dev": "bun bin/bertui.js dev",
|
|
@@ -38,10 +32,10 @@
|
|
|
38
32
|
"elysia",
|
|
39
33
|
"build-tool",
|
|
40
34
|
"bundler",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
35
|
+
"fast",
|
|
36
|
+
"hmr"
|
|
43
37
|
],
|
|
44
|
-
"author": "
|
|
38
|
+
"author": "Pease Ernest",
|
|
45
39
|
"license": "MIT",
|
|
46
40
|
"repository": {
|
|
47
41
|
"type": "git",
|
|
@@ -49,11 +43,17 @@
|
|
|
49
43
|
},
|
|
50
44
|
"dependencies": {
|
|
51
45
|
"elysia": "^1.0.0",
|
|
52
|
-
"ernest-logger": "latest"
|
|
46
|
+
"ernest-logger": "latest",
|
|
47
|
+
"postcss": "^8.4.32",
|
|
48
|
+
"autoprefixer": "^10.4.16",
|
|
49
|
+
"cssnano": "^6.0.2"
|
|
53
50
|
},
|
|
54
51
|
"peerDependencies": {
|
|
55
52
|
"react": "^18.0.0 || ^19.0.0",
|
|
56
53
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
57
54
|
"bun": ">=1.0.0"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"bun": ">=1.0.0"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/build/css-builder.js
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
4
|
+
import postcss from 'postcss';
|
|
5
|
+
import autoprefixer from 'autoprefixer';
|
|
6
|
+
import cssnano from 'cssnano';
|
|
7
|
+
import logger from '../logger/logger.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Build and minify CSS for production
|
|
11
|
+
* @param {string} srcPath - Source CSS file path
|
|
12
|
+
* @param {string} destPath - Destination CSS file path
|
|
13
|
+
*/
|
|
14
|
+
export async function buildCSS(srcPath, destPath) {
|
|
15
|
+
try {
|
|
16
|
+
logger.info('Processing CSS...');
|
|
17
|
+
|
|
18
|
+
// Ensure destination directory exists
|
|
19
|
+
const destDir = join(destPath, '..');
|
|
20
|
+
if (!existsSync(destDir)) {
|
|
21
|
+
mkdirSync(destDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Read source CSS
|
|
25
|
+
const css = await Bun.file(srcPath).text();
|
|
26
|
+
|
|
27
|
+
// Process with PostCSS
|
|
28
|
+
const result = await postcss([
|
|
29
|
+
autoprefixer(),
|
|
30
|
+
cssnano({
|
|
31
|
+
preset: ['default', {
|
|
32
|
+
discardComments: { removeAll: true },
|
|
33
|
+
normalizeWhitespace: true,
|
|
34
|
+
colormin: true,
|
|
35
|
+
minifyFontValues: true,
|
|
36
|
+
minifySelectors: true,
|
|
37
|
+
}]
|
|
38
|
+
})
|
|
39
|
+
]).process(css, { from: srcPath, to: destPath });
|
|
40
|
+
|
|
41
|
+
// Write minified CSS
|
|
42
|
+
await Bun.write(destPath, result.css);
|
|
43
|
+
|
|
44
|
+
// Calculate size reduction
|
|
45
|
+
const originalSize = (Buffer.byteLength(css) / 1024).toFixed(2);
|
|
46
|
+
const minifiedSize = (Buffer.byteLength(result.css) / 1024).toFixed(2);
|
|
47
|
+
const reduction = ((1 - Buffer.byteLength(result.css) / Buffer.byteLength(css)) * 100).toFixed(1);
|
|
48
|
+
|
|
49
|
+
logger.success(`CSS minified: ${originalSize}KB → ${minifiedSize}KB (-${reduction}%)`);
|
|
50
|
+
|
|
51
|
+
if (result.warnings().length > 0) {
|
|
52
|
+
result.warnings().forEach(warn => {
|
|
53
|
+
logger.warn(warn.toString());
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { success: true, size: minifiedSize };
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.error(`CSS build failed: ${error.message}`);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Copy CSS without minification (for dev)
|
|
66
|
+
* @param {string} srcPath - Source CSS file path
|
|
67
|
+
* @param {string} destPath - Destination CSS file path
|
|
68
|
+
*/
|
|
69
|
+
export async function copyCSS(srcPath, destPath) {
|
|
70
|
+
try {
|
|
71
|
+
const destDir = join(destPath, '..');
|
|
72
|
+
if (!existsSync(destDir)) {
|
|
73
|
+
mkdirSync(destDir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await Bun.write(destPath, Bun.file(srcPath));
|
|
77
|
+
logger.info('CSS copied for development');
|
|
78
|
+
|
|
79
|
+
return { success: true };
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger.error(`CSS copy failed: ${error.message}`);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/build.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// src/build.js
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import { existsSync, mkdirSync, rmSync } from 'fs';
|
|
3
|
+
import { existsSync, mkdirSync, rmSync, cpSync } from 'fs';
|
|
4
4
|
import logger from './logger/logger.js';
|
|
5
|
+
import { buildCSS } from './build/css-builder.js';
|
|
5
6
|
|
|
6
7
|
export async function buildProduction(options = {}) {
|
|
7
8
|
const root = options.root || process.cwd();
|
|
@@ -19,10 +20,34 @@ export async function buildProduction(options = {}) {
|
|
|
19
20
|
const startTime = Date.now();
|
|
20
21
|
|
|
21
22
|
try {
|
|
22
|
-
// Build
|
|
23
|
+
// Step 1: Build CSS from BertUI library
|
|
24
|
+
logger.info('Step 1: Building CSS...');
|
|
25
|
+
const bertuiCssSource = join(import.meta.dir, 'styles/bertui.css');
|
|
26
|
+
const bertuiCssDest = join(outDir, 'styles/bertui.min.css');
|
|
27
|
+
await buildCSS(bertuiCssSource, bertuiCssDest);
|
|
28
|
+
|
|
29
|
+
// Step 2: Copy public assets if they exist
|
|
30
|
+
const publicDir = join(root, 'public');
|
|
31
|
+
if (existsSync(publicDir)) {
|
|
32
|
+
logger.info('Step 2: Copying public assets...');
|
|
33
|
+
cpSync(publicDir, outDir, { recursive: true });
|
|
34
|
+
logger.success('Public assets copied');
|
|
35
|
+
} else {
|
|
36
|
+
logger.info('Step 2: No public directory found, skipping...');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Step 3: Build JavaScript with Bun's bundler
|
|
40
|
+
logger.info('Step 3: Bundling JavaScript...');
|
|
41
|
+
const mainEntry = join(root, 'src/main.jsx');
|
|
42
|
+
|
|
43
|
+
if (!existsSync(mainEntry)) {
|
|
44
|
+
logger.error('Entry point not found: src/main.jsx');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
23
48
|
const result = await Bun.build({
|
|
24
|
-
entrypoints: [
|
|
25
|
-
outdir: outDir,
|
|
49
|
+
entrypoints: [mainEntry],
|
|
50
|
+
outdir: join(outDir, 'assets'),
|
|
26
51
|
target: 'browser',
|
|
27
52
|
minify: true,
|
|
28
53
|
splitting: true,
|
|
@@ -30,35 +55,38 @@ export async function buildProduction(options = {}) {
|
|
|
30
55
|
naming: {
|
|
31
56
|
entry: '[name]-[hash].js',
|
|
32
57
|
chunk: 'chunks/[name]-[hash].js',
|
|
33
|
-
asset: '
|
|
34
|
-
}
|
|
58
|
+
asset: '[name]-[hash].[ext]'
|
|
59
|
+
},
|
|
60
|
+
external: [] // Don't externalize anything for browser builds
|
|
35
61
|
});
|
|
36
62
|
|
|
37
63
|
if (!result.success) {
|
|
38
|
-
logger.error('
|
|
64
|
+
logger.error('JavaScript build failed!');
|
|
39
65
|
result.logs.forEach(log => logger.error(log.message));
|
|
40
66
|
process.exit(1);
|
|
41
67
|
}
|
|
42
68
|
|
|
43
|
-
|
|
44
|
-
const bertuiCss = join(import.meta.dir, 'styles/bertui.css');
|
|
45
|
-
const destCss = join(outDir, 'bertui.css');
|
|
46
|
-
await Bun.write(destCss, Bun.file(bertuiCss));
|
|
47
|
-
logger.info('Copied BertUI CSS');
|
|
69
|
+
logger.success('JavaScript bundled');
|
|
48
70
|
|
|
49
|
-
// Generate index.html
|
|
71
|
+
// Step 4: Generate index.html
|
|
72
|
+
logger.info('Step 4: Generating index.html...');
|
|
50
73
|
await generateProductionHTML(root, outDir, result);
|
|
51
74
|
|
|
52
75
|
const duration = Date.now() - startTime;
|
|
53
|
-
logger.success(
|
|
54
|
-
logger.info(
|
|
76
|
+
logger.success(`✨ Build complete in ${duration}ms`);
|
|
77
|
+
logger.info(`📦 Output: ${outDir}`);
|
|
78
|
+
|
|
79
|
+
// Display build stats
|
|
55
80
|
logger.table(result.outputs.map(o => ({
|
|
56
|
-
file: o.path,
|
|
81
|
+
file: o.path.replace(outDir, ''),
|
|
57
82
|
size: `${(o.size / 1024).toFixed(2)} KB`
|
|
58
83
|
})));
|
|
59
84
|
|
|
60
85
|
} catch (error) {
|
|
61
86
|
logger.error(`Build failed: ${error.message}`);
|
|
87
|
+
if (error.stack) {
|
|
88
|
+
logger.error(error.stack);
|
|
89
|
+
}
|
|
62
90
|
process.exit(1);
|
|
63
91
|
}
|
|
64
92
|
}
|
|
@@ -70,25 +98,26 @@ async function generateProductionHTML(root, outDir, buildResult) {
|
|
|
70
98
|
);
|
|
71
99
|
|
|
72
100
|
if (!mainBundle) {
|
|
73
|
-
throw new Error('Could not find main bundle');
|
|
101
|
+
throw new Error('Could not find main bundle in build output');
|
|
74
102
|
}
|
|
75
103
|
|
|
76
|
-
const
|
|
104
|
+
const bundlePath = mainBundle.path.replace(outDir, '').replace(/^\//, '');
|
|
77
105
|
|
|
78
106
|
const html = `<!DOCTYPE html>
|
|
79
107
|
<html lang="en">
|
|
80
108
|
<head>
|
|
81
109
|
<meta charset="UTF-8">
|
|
82
110
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
111
|
+
<meta name="description" content="Built with BertUI - Lightning fast React development">
|
|
83
112
|
<title>BertUI App</title>
|
|
84
|
-
<link rel="stylesheet" href="/bertui.css">
|
|
113
|
+
<link rel="stylesheet" href="/styles/bertui.min.css">
|
|
85
114
|
</head>
|
|
86
115
|
<body>
|
|
87
116
|
<div id="root"></div>
|
|
88
|
-
<script type="module" src="/${
|
|
117
|
+
<script type="module" src="/${bundlePath}"></script>
|
|
89
118
|
</body>
|
|
90
119
|
</html>`;
|
|
91
120
|
|
|
92
121
|
await Bun.write(join(outDir, 'index.html'), html);
|
|
93
|
-
logger.
|
|
122
|
+
logger.success('Generated index.html');
|
|
94
123
|
}
|
package/src/client/compiler.js
CHANGED
|
@@ -51,40 +51,39 @@ export async function compileProject(root) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
// Update the Bun.build section in buildProduction function
|
|
55
|
+
|
|
56
|
+
// Build with Bun's bundler
|
|
57
|
+
const result = await Bun.build({
|
|
58
|
+
entrypoints: [join(root, 'src/main.jsx')],
|
|
59
|
+
outdir: outDir,
|
|
60
|
+
target: 'browser',
|
|
61
|
+
minify: true,
|
|
62
|
+
splitting: true,
|
|
63
|
+
sourcemap: 'external',
|
|
64
|
+
naming: {
|
|
65
|
+
entry: '[name]-[hash].js',
|
|
66
|
+
chunk: 'chunks/[name]-[hash].js',
|
|
67
|
+
asset: 'assets/[name]-[hash].[ext]'
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!result.success) {
|
|
72
|
+
logger.error('Build failed!');
|
|
73
|
+
|
|
74
|
+
// Log detailed errors
|
|
75
|
+
if (result.logs && result.logs.length > 0) {
|
|
76
|
+
logger.error('Detailed errors:');
|
|
77
|
+
result.logs.forEach((log, index) => {
|
|
78
|
+
logger.error(` [${index + 1}] ${log.message}`);
|
|
79
|
+
if (log.location) {
|
|
80
|
+
logger.error(` at ${log.location.file}:${log.location.line}:${log.location.column}`);
|
|
68
81
|
}
|
|
69
82
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
throw new Error('Build failed');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Copy BertUI CSS
|
|
78
|
-
const bertuiCss = join(import.meta.dir, '../styles/bertui.css');
|
|
79
|
-
if (existsSync(bertuiCss)) {
|
|
80
|
-
await Bun.write(join(compiledDir, 'bertui.css'), Bun.file(bertuiCss));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const duration = Date.now() - startTime;
|
|
84
|
-
logger.success(`Compiled in ${duration}ms`);
|
|
85
|
-
|
|
86
|
-
return { success: true, routes };
|
|
87
|
-
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
88
87
|
} catch (error) {
|
|
89
88
|
logger.error(`Compilation error: ${error.message}`);
|
|
90
89
|
throw error;
|
package/src/config/loadConfig.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
// src/config/loadConfig.js
|
|
1
2
|
import { join } from 'path';
|
|
2
3
|
import { existsSync } from 'fs';
|
|
3
4
|
import { defaultConfig } from './defaultConfig.js';
|
|
4
|
-
import logger from '../
|
|
5
|
+
import logger from '../logger/logger.js';
|
|
5
6
|
|
|
6
7
|
export async function loadConfig(root) {
|
|
7
8
|
const configPath = join(root, 'bertui.config.js');
|
|
@@ -15,7 +16,7 @@ export async function loadConfig(root) {
|
|
|
15
16
|
// Merge user config with defaults
|
|
16
17
|
return mergeConfig(defaultConfig, userConfig.default || userConfig);
|
|
17
18
|
} catch (error) {
|
|
18
|
-
logger.error(`Failed to load config
|
|
19
|
+
logger.error(`Failed to load config. Make sure bertui.config.js is in the root directory: ${error.message}`);
|
|
19
20
|
return defaultConfig;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
@@ -26,7 +27,7 @@ export async function loadConfig(root) {
|
|
|
26
27
|
|
|
27
28
|
function mergeConfig(defaults, user) {
|
|
28
29
|
return {
|
|
29
|
-
meta: { ...defaults.meta, ...user.meta },
|
|
30
|
-
appShell: { ...defaults.appShell, ...user.appShell }
|
|
30
|
+
meta: { ...defaults.meta, ...(user.meta || {}) },
|
|
31
|
+
appShell: { ...defaults.appShell, ...(user.appShell || {}) }
|
|
31
32
|
};
|
|
32
33
|
}
|
package/src/server/dev-server.js
CHANGED
|
@@ -12,40 +12,22 @@ export async function startDevServer(options = {}) {
|
|
|
12
12
|
const compiledDir = join(root, '.bertui', 'compiled');
|
|
13
13
|
|
|
14
14
|
const clients = new Set();
|
|
15
|
-
let currentRoutes = [];
|
|
16
15
|
|
|
17
16
|
const app = new Elysia()
|
|
18
|
-
|
|
19
|
-
.get('/*', async ({ params }) => {
|
|
20
|
-
// Check if it's requesting a file
|
|
21
|
-
const path = params['*'] || '';
|
|
22
|
-
|
|
23
|
-
if (path.includes('.')) {
|
|
24
|
-
// It's a file request, handle it separately
|
|
25
|
-
return await serveFile(compiledDir, path);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Serve the SPA HTML for all routes
|
|
17
|
+
.get('/', async () => {
|
|
29
18
|
const html = `
|
|
30
19
|
<!DOCTYPE html>
|
|
31
20
|
<html lang="en">
|
|
32
21
|
<head>
|
|
33
22
|
<meta charset="UTF-8">
|
|
34
23
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
35
|
-
<title>BertUI App</title>
|
|
36
|
-
<link rel="stylesheet" href="/
|
|
24
|
+
<title>BertUI App - Dev</title>
|
|
25
|
+
<link rel="stylesheet" href="/styles/bertui.css">
|
|
37
26
|
</head>
|
|
38
27
|
<body>
|
|
39
28
|
<div id="root"></div>
|
|
40
29
|
<script type="module" src="/hmr-client.js"></script>
|
|
41
|
-
<script type="module">
|
|
42
|
-
// Provide React and ReactDOM from CDN for dev
|
|
43
|
-
import React from 'https://esm.sh/react@18.2.0';
|
|
44
|
-
import ReactDOM from 'https://esm.sh/react-dom@18.2.0';
|
|
45
|
-
window.React = React;
|
|
46
|
-
window.ReactDOM = ReactDOM;
|
|
47
|
-
</script>
|
|
48
|
-
<script type="module" src="/compiled/main-entry.js"></script>
|
|
30
|
+
<script type="module" src="/compiled/main.js"></script>
|
|
49
31
|
</body>
|
|
50
32
|
</html>`;
|
|
51
33
|
|
|
@@ -66,28 +48,21 @@ ws.onmessage = (event) => {
|
|
|
66
48
|
const data = JSON.parse(event.data);
|
|
67
49
|
|
|
68
50
|
if (data.type === 'reload') {
|
|
69
|
-
console.log('%c🔄
|
|
51
|
+
console.log('%c🔄 Reloading...', 'color: #f59e0b');
|
|
70
52
|
window.location.reload();
|
|
71
53
|
}
|
|
72
54
|
|
|
73
55
|
if (data.type === 'recompiling') {
|
|
74
56
|
console.log('%c⚙️ Recompiling...', 'color: #3b82f6');
|
|
75
57
|
}
|
|
76
|
-
|
|
77
|
-
if (data.type === 'routes-updated') {
|
|
78
|
-
console.log('%c📍 Routes updated:', 'color: #8b5cf6; font-weight: bold');
|
|
79
|
-
data.routes.forEach(r => {
|
|
80
|
-
console.log(\` \${r.path} → \${r.file}\`);
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
58
|
};
|
|
84
59
|
|
|
85
60
|
ws.onerror = (error) => {
|
|
86
|
-
console.error('HMR connection error:', error);
|
|
61
|
+
console.error('%c❌ HMR connection error', 'color: #ef4444', error);
|
|
87
62
|
};
|
|
88
63
|
|
|
89
64
|
ws.onclose = () => {
|
|
90
|
-
console.log('%c
|
|
65
|
+
console.log('%c⚠️ HMR disconnected. Refresh to reconnect.', 'color: #f59e0b');
|
|
91
66
|
};
|
|
92
67
|
`;
|
|
93
68
|
|
|
@@ -100,18 +75,29 @@ ws.onclose = () => {
|
|
|
100
75
|
open(ws) {
|
|
101
76
|
clients.add(ws);
|
|
102
77
|
logger.info('Client connected to HMR');
|
|
103
|
-
|
|
104
|
-
// Send current routes on connection
|
|
105
|
-
if (currentRoutes.length > 0) {
|
|
106
|
-
ws.send(JSON.stringify({
|
|
107
|
-
type: 'routes-updated',
|
|
108
|
-
routes: currentRoutes
|
|
109
|
-
}));
|
|
110
|
-
}
|
|
111
78
|
},
|
|
112
79
|
close(ws) {
|
|
113
80
|
clients.delete(ws);
|
|
81
|
+
logger.info('Client disconnected from HMR');
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// Serve BertUI CSS
|
|
86
|
+
.get('/styles/bertui.css', async ({ set }) => {
|
|
87
|
+
const cssPath = join(import.meta.dir, '../styles/bertui.css');
|
|
88
|
+
const file = Bun.file(cssPath);
|
|
89
|
+
|
|
90
|
+
if (!await file.exists()) {
|
|
91
|
+
set.status = 404;
|
|
92
|
+
return 'CSS file not found';
|
|
114
93
|
}
|
|
94
|
+
|
|
95
|
+
return new Response(await file.text(), {
|
|
96
|
+
headers: {
|
|
97
|
+
'Content-Type': 'text/css',
|
|
98
|
+
'Cache-Control': 'no-store'
|
|
99
|
+
}
|
|
100
|
+
});
|
|
115
101
|
})
|
|
116
102
|
|
|
117
103
|
// Serve compiled files
|
|
@@ -125,7 +111,9 @@ ws.onclose = () => {
|
|
|
125
111
|
}
|
|
126
112
|
|
|
127
113
|
const ext = extname(filepath);
|
|
128
|
-
const contentType =
|
|
114
|
+
const contentType = ext === '.js' ? 'application/javascript' :
|
|
115
|
+
ext === '.css' ? 'text/css' :
|
|
116
|
+
'text/plain';
|
|
129
117
|
|
|
130
118
|
return new Response(await file.text(), {
|
|
131
119
|
headers: {
|
|
@@ -135,6 +123,20 @@ ws.onclose = () => {
|
|
|
135
123
|
});
|
|
136
124
|
})
|
|
137
125
|
|
|
126
|
+
// Serve public assets
|
|
127
|
+
.get('/public/*', async ({ params, set }) => {
|
|
128
|
+
const publicDir = join(root, 'public');
|
|
129
|
+
const filepath = join(publicDir, params['*']);
|
|
130
|
+
const file = Bun.file(filepath);
|
|
131
|
+
|
|
132
|
+
if (!await file.exists()) {
|
|
133
|
+
set.status = 404;
|
|
134
|
+
return 'File not found';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return new Response(file);
|
|
138
|
+
})
|
|
139
|
+
|
|
138
140
|
.listen(port);
|
|
139
141
|
|
|
140
142
|
if (!app.server) {
|
|
@@ -142,50 +144,16 @@ ws.onclose = () => {
|
|
|
142
144
|
process.exit(1);
|
|
143
145
|
}
|
|
144
146
|
|
|
145
|
-
logger.success(
|
|
146
|
-
logger.info(
|
|
147
|
+
logger.success(`🚀 Server running at http://localhost:${port}`);
|
|
148
|
+
logger.info(`📁 Serving: ${root}`);
|
|
147
149
|
|
|
148
150
|
// Watch for file changes
|
|
149
|
-
setupWatcher(root,
|
|
150
|
-
currentRoutes = routes;
|
|
151
|
-
});
|
|
151
|
+
setupWatcher(root, compiledDir, clients);
|
|
152
152
|
|
|
153
153
|
return app;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
const filepath = join(compiledDir, path);
|
|
158
|
-
const file = Bun.file(filepath);
|
|
159
|
-
|
|
160
|
-
if (!await file.exists()) {
|
|
161
|
-
return new Response('File not found', { status: 404 });
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const ext = extname(filepath);
|
|
165
|
-
const contentType = getContentType(ext);
|
|
166
|
-
|
|
167
|
-
return new Response(await file.text(), {
|
|
168
|
-
headers: {
|
|
169
|
-
'Content-Type': contentType,
|
|
170
|
-
'Cache-Control': 'no-store'
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function getContentType(ext) {
|
|
176
|
-
const types = {
|
|
177
|
-
'.js': 'application/javascript',
|
|
178
|
-
'.css': 'text/css',
|
|
179
|
-
'.html': 'text/html',
|
|
180
|
-
'.json': 'application/json',
|
|
181
|
-
'.png': 'image/png',
|
|
182
|
-
'.jpg': 'image/jpeg',
|
|
183
|
-
'.svg': 'image/svg+xml'
|
|
184
|
-
};
|
|
185
|
-
return types[ext] || 'text/plain';
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function setupWatcher(root, clients, onRoutesUpdate) {
|
|
156
|
+
function setupWatcher(root, compiledDir, clients) {
|
|
189
157
|
const srcDir = join(root, 'src');
|
|
190
158
|
|
|
191
159
|
if (!existsSync(srcDir)) {
|
|
@@ -195,15 +163,11 @@ function setupWatcher(root, clients, onRoutesUpdate) {
|
|
|
195
163
|
|
|
196
164
|
logger.info(`👀 Watching: ${srcDir}`);
|
|
197
165
|
|
|
198
|
-
let isRecompiling = false;
|
|
199
|
-
|
|
200
166
|
watch(srcDir, { recursive: true }, async (eventType, filename) => {
|
|
201
|
-
if (!filename
|
|
167
|
+
if (!filename) return;
|
|
202
168
|
|
|
203
169
|
const ext = extname(filename);
|
|
204
170
|
if (['.js', '.jsx', '.ts', '.tsx', '.css'].includes(ext)) {
|
|
205
|
-
isRecompiling = true;
|
|
206
|
-
|
|
207
171
|
logger.info(`📝 File changed: ${filename}`);
|
|
208
172
|
|
|
209
173
|
// Notify clients that recompilation is starting
|
|
@@ -217,23 +181,7 @@ function setupWatcher(root, clients, onRoutesUpdate) {
|
|
|
217
181
|
|
|
218
182
|
// Recompile the project
|
|
219
183
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// Notify about route changes
|
|
223
|
-
if (result.routes && result.routes.length > 0) {
|
|
224
|
-
onRoutesUpdate(result.routes);
|
|
225
|
-
|
|
226
|
-
for (const client of clients) {
|
|
227
|
-
try {
|
|
228
|
-
client.send(JSON.stringify({
|
|
229
|
-
type: 'routes-updated',
|
|
230
|
-
routes: result.routes
|
|
231
|
-
}));
|
|
232
|
-
} catch (e) {
|
|
233
|
-
clients.delete(client);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
184
|
+
await compileProject(root);
|
|
237
185
|
|
|
238
186
|
// Notify clients to reload
|
|
239
187
|
for (const client of clients) {
|
|
@@ -245,8 +193,6 @@ function setupWatcher(root, clients, onRoutesUpdate) {
|
|
|
245
193
|
}
|
|
246
194
|
} catch (error) {
|
|
247
195
|
logger.error(`Recompilation failed: ${error.message}`);
|
|
248
|
-
} finally {
|
|
249
|
-
isRecompiling = false;
|
|
250
196
|
}
|
|
251
197
|
}
|
|
252
198
|
});
|