bertui 0.1.0
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 +21 -0
- package/README.md +52 -0
- package/bin/bertui.js +8 -0
- package/package.json +50 -0
- package/src/build.js +94 -0
- package/src/cli.js +66 -0
- package/src/client/compiler.js +94 -0
- package/src/config/defaultConfig.js +16 -0
- package/src/config/loadConfig.js +32 -0
- package/src/config/og-image.png +0 -0
- package/src/dev.js +23 -0
- package/src/logger/logger.js +18 -0
- package/src/logger/notes.md +20 -0
- package/src/server/dev-server.js +154 -0
- package/src/server/notes.md +1 -0
- package/src/styles/bertui.css +210 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 BunElysiaReact
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# BertUI ⚡
|
|
2
|
+
|
|
3
|
+
Lightning-fast React development powered by Bun.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ⚡ **Blazing Fast** - Built on Bun
|
|
8
|
+
- 🎨 **Built-in Animations** - 15+ CSS utility classes
|
|
9
|
+
- 🔥 **Hot Module Replacement** - Instant updates
|
|
10
|
+
- 📦 **Zero Config** - Works out of the box
|
|
11
|
+
- 🚀 **Production Ready** - Optimized builds
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
```bash
|
|
15
|
+
bun add bertui react react-dom
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
```javascript
|
|
20
|
+
// src/main.jsx
|
|
21
|
+
import 'bertui/styles';
|
|
22
|
+
import React from 'react';
|
|
23
|
+
import ReactDOM from 'react-dom/client';
|
|
24
|
+
|
|
25
|
+
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
26
|
+
<h1 className="split fadein">Hello BertUI!</h1>
|
|
27
|
+
);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
```bash
|
|
32
|
+
bertui dev # Start dev server
|
|
33
|
+
bertui build # Build for production
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## CSS Classes
|
|
37
|
+
|
|
38
|
+
- `.split` - Split text animation
|
|
39
|
+
- `.moveright` - Slide from left
|
|
40
|
+
- `.moveleft` - Slide from right
|
|
41
|
+
- `.fadein` - Fade in
|
|
42
|
+
- `.scalein` - Scale in
|
|
43
|
+
- `.bouncein` - Bounce in
|
|
44
|
+
- `.slideup` - Slide up
|
|
45
|
+
- `.slidedown` - Slide down
|
|
46
|
+
- `.rotatein` - Rotate in
|
|
47
|
+
- `.pulse` - Pulse animation
|
|
48
|
+
- `.shake` - Shake animation
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT
|
package/bin/bertui.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bertui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightning-fast React dev server powered by Bun and Elysia",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"bertui": "./bin/bertui.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./index.js",
|
|
12
|
+
"./styles": "./src/styles/bertui.css",
|
|
13
|
+
"./logger": "./src/logger/logger.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin",
|
|
17
|
+
"src",
|
|
18
|
+
"index.ts",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "bun bin/bertui.js dev",
|
|
23
|
+
"build": "bun bin/bertui.js build",
|
|
24
|
+
"test": "cd test-app && bun run dev"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react",
|
|
28
|
+
"bun",
|
|
29
|
+
"dev-server",
|
|
30
|
+
"vite-alternative",
|
|
31
|
+
"elysia",
|
|
32
|
+
"build-tool",
|
|
33
|
+
"bundler"
|
|
34
|
+
],
|
|
35
|
+
"author": "Your Name",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/BunElysiaReact/BERTUI.git"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"elysia": "^1.0.0",
|
|
43
|
+
"ernest-logger": "latest"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
47
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
48
|
+
"bun": ">=1.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/build.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/build.js
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync, mkdirSync, rmSync } from 'fs';
|
|
4
|
+
import logger from './logger/logger.js';
|
|
5
|
+
|
|
6
|
+
export async function buildProduction(options = {}) {
|
|
7
|
+
const root = options.root || process.cwd();
|
|
8
|
+
const outDir = join(root, 'dist');
|
|
9
|
+
|
|
10
|
+
logger.bigLog('BUILDING FOR PRODUCTION', { color: 'green' });
|
|
11
|
+
|
|
12
|
+
// Clean dist folder
|
|
13
|
+
if (existsSync(outDir)) {
|
|
14
|
+
rmSync(outDir, { recursive: true });
|
|
15
|
+
logger.info('Cleaned dist/');
|
|
16
|
+
}
|
|
17
|
+
mkdirSync(outDir, { recursive: true });
|
|
18
|
+
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Build with Bun's bundler
|
|
23
|
+
const result = await Bun.build({
|
|
24
|
+
entrypoints: [join(root, 'src/main.jsx')],
|
|
25
|
+
outdir: outDir,
|
|
26
|
+
target: 'browser',
|
|
27
|
+
minify: true,
|
|
28
|
+
splitting: true,
|
|
29
|
+
sourcemap: 'external',
|
|
30
|
+
naming: {
|
|
31
|
+
entry: '[name]-[hash].js',
|
|
32
|
+
chunk: 'chunks/[name]-[hash].js',
|
|
33
|
+
asset: 'assets/[name]-[hash].[ext]'
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!result.success) {
|
|
38
|
+
logger.error('Build failed!');
|
|
39
|
+
result.logs.forEach(log => logger.error(log.message));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Copy BertUI CSS to dist
|
|
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');
|
|
48
|
+
|
|
49
|
+
// Generate index.html
|
|
50
|
+
await generateProductionHTML(root, outDir, result);
|
|
51
|
+
|
|
52
|
+
const duration = Date.now() - startTime;
|
|
53
|
+
logger.success(`Build complete in ${duration}ms`);
|
|
54
|
+
logger.info(`Output: ${outDir}`);
|
|
55
|
+
logger.table(result.outputs.map(o => ({
|
|
56
|
+
file: o.path,
|
|
57
|
+
size: `${(o.size / 1024).toFixed(2)} KB`
|
|
58
|
+
})));
|
|
59
|
+
|
|
60
|
+
} catch (error) {
|
|
61
|
+
logger.error(`Build failed: ${error.message}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function generateProductionHTML(root, outDir, buildResult) {
|
|
67
|
+
// Find the main bundle
|
|
68
|
+
const mainBundle = buildResult.outputs.find(o =>
|
|
69
|
+
o.path.includes('main') && o.kind === 'entry-point'
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (!mainBundle) {
|
|
73
|
+
throw new Error('Could not find main bundle');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const bundleName = mainBundle.path.split('/').pop();
|
|
77
|
+
|
|
78
|
+
const html = `<!DOCTYPE html>
|
|
79
|
+
<html lang="en">
|
|
80
|
+
<head>
|
|
81
|
+
<meta charset="UTF-8">
|
|
82
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
83
|
+
<title>BertUI App</title>
|
|
84
|
+
<link rel="stylesheet" href="/bertui.css">
|
|
85
|
+
</head>
|
|
86
|
+
<body>
|
|
87
|
+
<div id="root"></div>
|
|
88
|
+
<script type="module" src="/${bundleName}"></script>
|
|
89
|
+
</body>
|
|
90
|
+
</html>`;
|
|
91
|
+
|
|
92
|
+
await Bun.write(join(outDir, 'index.html'), html);
|
|
93
|
+
logger.info('Generated index.html');
|
|
94
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/cli.js
|
|
2
|
+
import { startDev } from './dev.js';
|
|
3
|
+
import { buildProduction } from './build.js';
|
|
4
|
+
import logger from './logger/logger.js';
|
|
5
|
+
|
|
6
|
+
export function program() {
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const command = args[0] || 'dev';
|
|
9
|
+
|
|
10
|
+
switch (command) {
|
|
11
|
+
case 'dev':
|
|
12
|
+
const devPort = getArg('--port', '-p') || 3000;
|
|
13
|
+
startDev({
|
|
14
|
+
port: parseInt(devPort),
|
|
15
|
+
root: process.cwd()
|
|
16
|
+
});
|
|
17
|
+
break;
|
|
18
|
+
|
|
19
|
+
case 'build':
|
|
20
|
+
buildProduction({
|
|
21
|
+
root: process.cwd()
|
|
22
|
+
});
|
|
23
|
+
break;
|
|
24
|
+
|
|
25
|
+
case '--version':
|
|
26
|
+
case '-v':
|
|
27
|
+
console.log('bertui v0.1.0');
|
|
28
|
+
break;
|
|
29
|
+
|
|
30
|
+
case '--help':
|
|
31
|
+
case '-h':
|
|
32
|
+
showHelp();
|
|
33
|
+
break;
|
|
34
|
+
|
|
35
|
+
default:
|
|
36
|
+
logger.error(`Unknown command: ${command}`);
|
|
37
|
+
showHelp();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getArg(longForm, shortForm) {
|
|
42
|
+
const args = process.argv.slice(2);
|
|
43
|
+
const longIndex = args.indexOf(longForm);
|
|
44
|
+
const shortIndex = args.indexOf(shortForm);
|
|
45
|
+
const index = longIndex !== -1 ? longIndex : shortIndex;
|
|
46
|
+
return index !== -1 && args[index + 1] ? args[index + 1] : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function showHelp() {
|
|
50
|
+
logger.bigLog('BERTUI CLI', { color: 'blue' });
|
|
51
|
+
console.log(`
|
|
52
|
+
Commands:
|
|
53
|
+
bertui dev Start development server
|
|
54
|
+
bertui build Build for production
|
|
55
|
+
bertui --version Show version
|
|
56
|
+
bertui --help Show help
|
|
57
|
+
|
|
58
|
+
Options:
|
|
59
|
+
--port, -p <number> Port for dev server (default: 3000)
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
bertui dev
|
|
63
|
+
bertui dev --port 8080
|
|
64
|
+
bertui build
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/client/compiler.js
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
3
|
+
import { join, extname, relative } from 'path';
|
|
4
|
+
import logger from '../logger/logger.js';
|
|
5
|
+
|
|
6
|
+
export async function compileProject(root) {
|
|
7
|
+
logger.bigLog('COMPILING PROJECT', { color: 'blue' });
|
|
8
|
+
|
|
9
|
+
const srcDir = join(root, 'src');
|
|
10
|
+
const outDir = join(root, '.bertui', 'compiled');
|
|
11
|
+
|
|
12
|
+
// Check if src exists
|
|
13
|
+
if (!existsSync(srcDir)) {
|
|
14
|
+
logger.error('src/ directory not found!');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Create output directory
|
|
19
|
+
if (!existsSync(outDir)) {
|
|
20
|
+
mkdirSync(outDir, { recursive: true });
|
|
21
|
+
logger.info('Created .bertui/compiled/');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Compile all files
|
|
25
|
+
const startTime = Date.now();
|
|
26
|
+
const stats = await compileDirectory(srcDir, outDir, root);
|
|
27
|
+
const duration = Date.now() - startTime;
|
|
28
|
+
|
|
29
|
+
logger.success(`Compiled ${stats.files} files in ${duration}ms`);
|
|
30
|
+
logger.info(`Output: ${outDir}`);
|
|
31
|
+
|
|
32
|
+
return { outDir, stats };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function compileDirectory(srcDir, outDir, root) {
|
|
36
|
+
const stats = { files: 0, skipped: 0 };
|
|
37
|
+
|
|
38
|
+
const files = readdirSync(srcDir);
|
|
39
|
+
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
const srcPath = join(srcDir, file);
|
|
42
|
+
const stat = statSync(srcPath);
|
|
43
|
+
|
|
44
|
+
if (stat.isDirectory()) {
|
|
45
|
+
// Recursively compile subdirectories
|
|
46
|
+
const subOutDir = join(outDir, file);
|
|
47
|
+
mkdirSync(subOutDir, { recursive: true });
|
|
48
|
+
const subStats = await compileDirectory(srcPath, subOutDir, root);
|
|
49
|
+
stats.files += subStats.files;
|
|
50
|
+
stats.skipped += subStats.skipped;
|
|
51
|
+
} else {
|
|
52
|
+
// Compile file
|
|
53
|
+
const ext = extname(file);
|
|
54
|
+
const relativePath = relative(join(root, 'src'), srcPath);
|
|
55
|
+
|
|
56
|
+
if (['.jsx', '.tsx', '.ts'].includes(ext)) {
|
|
57
|
+
await compileFile(srcPath, outDir, file, relativePath);
|
|
58
|
+
stats.files++;
|
|
59
|
+
} else if (ext === '.js' || ext === '.css') {
|
|
60
|
+
// Copy as-is
|
|
61
|
+
const outPath = join(outDir, file);
|
|
62
|
+
await Bun.write(outPath, Bun.file(srcPath));
|
|
63
|
+
logger.debug(`Copied: ${relativePath}`);
|
|
64
|
+
stats.files++;
|
|
65
|
+
} else {
|
|
66
|
+
logger.debug(`Skipped: ${relativePath}`);
|
|
67
|
+
stats.skipped++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return stats;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function compileFile(srcPath, outDir, filename, relativePath) {
|
|
76
|
+
const ext = extname(filename);
|
|
77
|
+
const loader = ext === '.tsx' ? 'tsx' : ext === '.ts' ? 'ts' : 'jsx';
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const transpiler = new Bun.Transpiler({ loader });
|
|
81
|
+
const code = await Bun.file(srcPath).text();
|
|
82
|
+
const compiled = await transpiler.transform(code);
|
|
83
|
+
|
|
84
|
+
// Change extension to .js
|
|
85
|
+
const outFilename = filename.replace(/\.(jsx|tsx|ts)$/, '.js');
|
|
86
|
+
const outPath = join(outDir, outFilename);
|
|
87
|
+
|
|
88
|
+
await Bun.write(outPath, compiled);
|
|
89
|
+
logger.debug(`Compiled: ${relativePath} → ${outFilename}`);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logger.error(`Failed to compile ${relativePath}: ${error.message}`);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const defaultConfig = {
|
|
2
|
+
meta: {
|
|
3
|
+
title: "BertUI App",
|
|
4
|
+
description: "Built with BertUI - Lightning fast React development",
|
|
5
|
+
keywords: "react, bun, bertui",
|
|
6
|
+
author: "Pease Ernest",
|
|
7
|
+
ogImage: "/og-image.png",
|
|
8
|
+
themeColor: "#f30606ff",
|
|
9
|
+
lang: "en"
|
|
10
|
+
},
|
|
11
|
+
appShell: {
|
|
12
|
+
loading: true,
|
|
13
|
+
loadingText: "Loading...",
|
|
14
|
+
backgroundColor: "#ffffff"
|
|
15
|
+
}
|
|
16
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { defaultConfig } from './defaultConfig.js';
|
|
4
|
+
import logger from '../utils/logger.js';
|
|
5
|
+
|
|
6
|
+
export async function loadConfig(root) {
|
|
7
|
+
const configPath = join(root, 'bertui.config.js');
|
|
8
|
+
|
|
9
|
+
// Check if user created config
|
|
10
|
+
if (existsSync(configPath)) {
|
|
11
|
+
try {
|
|
12
|
+
const userConfig = await import(configPath);
|
|
13
|
+
logger.success('Loaded bertui.config.js');
|
|
14
|
+
|
|
15
|
+
// Merge user config with defaults
|
|
16
|
+
return mergeConfig(defaultConfig, userConfig.default || userConfig);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
logger.error(`Failed to load config make sure the file bertui.config.js is in the root directory of the app if not create it : ${error.message}`);
|
|
19
|
+
return defaultConfig;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
logger.info('No config found, using defaults');
|
|
24
|
+
return defaultConfig;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function mergeConfig(defaults, user) {
|
|
28
|
+
return {
|
|
29
|
+
meta: { ...defaults.meta, ...user.meta },
|
|
30
|
+
appShell: { ...defaults.appShell, ...user.appShell }
|
|
31
|
+
};
|
|
32
|
+
}
|
|
Binary file
|
package/src/dev.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/dev.js
|
|
2
|
+
import { compileProject } from './client/compiler.js';
|
|
3
|
+
import { startDevServer } from './server/dev-server.js';
|
|
4
|
+
import logger from './logger/logger.js';
|
|
5
|
+
|
|
6
|
+
export async function startDev(options = {}) {
|
|
7
|
+
const root = options.root || process.cwd();
|
|
8
|
+
const port = options.port || 3000;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Step 1: Compile project
|
|
12
|
+
logger.info('Step 1: Compiling project...');
|
|
13
|
+
await compileProject(root);
|
|
14
|
+
|
|
15
|
+
// Step 2: Start dev server
|
|
16
|
+
logger.info('Step 2: Starting dev server...');
|
|
17
|
+
await startDevServer({ root, port });
|
|
18
|
+
|
|
19
|
+
} catch (error) {
|
|
20
|
+
logger.error(`Dev server failed: ${error.message}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/utils/logger.js
|
|
2
|
+
import { createLogger } from 'ernest-logger';
|
|
3
|
+
|
|
4
|
+
// Create logger instance for BertUI
|
|
5
|
+
const logger = createLogger({
|
|
6
|
+
time: true,
|
|
7
|
+
emoji: true,
|
|
8
|
+
level: 'info',
|
|
9
|
+
prefix: '[BertUI]',
|
|
10
|
+
customLevels: {
|
|
11
|
+
server: { color: 'brightCyan', emoji: '🌐', priority: 2 },
|
|
12
|
+
build: { color: 'brightGreen', emoji: '📦', priority: 2 },
|
|
13
|
+
hmr: { color: 'brightYellow', emoji: '🔥', priority: 2 }
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Export the logger
|
|
18
|
+
export default logger;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
here we will use the ernest logger libraray instead of chalk because i want colourfull logs and not just normal b/w logs the whole module is exported for use in the libraray
|
|
2
|
+
it is a file that is complete and needs no updated later on
|
|
3
|
+
// src/utils/logger.js
|
|
4
|
+
import { createLogger } from 'ernest-logger';
|
|
5
|
+
|
|
6
|
+
// Create logger instance for BertUI
|
|
7
|
+
const logger = createLogger({
|
|
8
|
+
time: true,
|
|
9
|
+
emoji: true,
|
|
10
|
+
level: 'info',
|
|
11
|
+
prefix: '[BertUI]',
|
|
12
|
+
customLevels: {
|
|
13
|
+
server: { color: 'brightCyan', emoji: '🌐', priority: 2 },
|
|
14
|
+
build: { color: 'brightGreen', emoji: '📦', priority: 2 },
|
|
15
|
+
hmr: { color: 'brightYellow', emoji: '🔥', priority: 2 }
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Export the logger
|
|
20
|
+
export default logger;
|
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
|
|
16
|
+
const app = new Elysia()
|
|
17
|
+
.get('/', async () => {
|
|
18
|
+
const html = `
|
|
19
|
+
<!DOCTYPE html>
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="UTF-8">
|
|
23
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
24
|
+
<title>BertUI App</title>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<div id="root"></div>
|
|
28
|
+
<script type="module" src="/hmr-client.js"></script>
|
|
29
|
+
<script type="module" src="/compiled/main.js"></script>
|
|
30
|
+
</body>
|
|
31
|
+
</html>`;
|
|
32
|
+
|
|
33
|
+
return new Response(html, {
|
|
34
|
+
headers: { 'Content-Type': 'text/html' }
|
|
35
|
+
});
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
.get('/hmr-client.js', () => {
|
|
39
|
+
const script = `
|
|
40
|
+
const ws = new WebSocket('ws://localhost:${port}/hmr');
|
|
41
|
+
|
|
42
|
+
ws.onopen = () => {
|
|
43
|
+
console.log('%c🔥 BertUI HMR connected', 'color: #10b981; font-weight: bold');
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
ws.onmessage = (event) => {
|
|
47
|
+
const data = JSON.parse(event.data);
|
|
48
|
+
|
|
49
|
+
if (data.type === 'reload') {
|
|
50
|
+
console.log('%c🔄 Reloading...', 'color: #f59e0b');
|
|
51
|
+
window.location.reload();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (data.type === 'recompiling') {
|
|
55
|
+
console.log('%c⚙️ Recompiling...', 'color: #3b82f6');
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
return new Response(script, {
|
|
61
|
+
headers: { 'Content-Type': 'application/javascript' }
|
|
62
|
+
});
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
.ws('/hmr', {
|
|
66
|
+
open(ws) {
|
|
67
|
+
clients.add(ws);
|
|
68
|
+
logger.info('Client connected to HMR');
|
|
69
|
+
},
|
|
70
|
+
close(ws) {
|
|
71
|
+
clients.delete(ws);
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Serve compiled files
|
|
76
|
+
.get('/compiled/*', async ({ params, set }) => {
|
|
77
|
+
const filepath = join(compiledDir, params['*']);
|
|
78
|
+
const file = Bun.file(filepath);
|
|
79
|
+
|
|
80
|
+
if (!await file.exists()) {
|
|
81
|
+
set.status = 404;
|
|
82
|
+
return 'File not found';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const ext = extname(filepath);
|
|
86
|
+
const contentType = ext === '.js' ? 'application/javascript' : 'text/plain';
|
|
87
|
+
|
|
88
|
+
return new Response(await file.text(), {
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': contentType,
|
|
91
|
+
'Cache-Control': 'no-store'
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
.listen(port);
|
|
97
|
+
|
|
98
|
+
if (!app.server) {
|
|
99
|
+
logger.error('Failed to start server');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
logger.success(`Server running at http://localhost:${port}`);
|
|
104
|
+
|
|
105
|
+
// Watch for file changes
|
|
106
|
+
setupWatcher(root, compiledDir, clients);
|
|
107
|
+
|
|
108
|
+
return app;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function setupWatcher(root, compiledDir, clients) {
|
|
112
|
+
const srcDir = join(root, 'src');
|
|
113
|
+
|
|
114
|
+
if (!existsSync(srcDir)) {
|
|
115
|
+
logger.warn('src/ directory not found');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
logger.info(`Watching: ${srcDir}`);
|
|
120
|
+
|
|
121
|
+
watch(srcDir, { recursive: true }, async (eventType, filename) => {
|
|
122
|
+
if (!filename) return;
|
|
123
|
+
|
|
124
|
+
const ext = extname(filename);
|
|
125
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.css'].includes(ext)) {
|
|
126
|
+
logger.info(`File changed: ${filename}`);
|
|
127
|
+
|
|
128
|
+
// Notify clients that recompilation is starting
|
|
129
|
+
for (const client of clients) {
|
|
130
|
+
try {
|
|
131
|
+
client.send(JSON.stringify({ type: 'recompiling' }));
|
|
132
|
+
} catch (e) {
|
|
133
|
+
clients.delete(client);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Recompile the project
|
|
138
|
+
try {
|
|
139
|
+
await compileProject(root);
|
|
140
|
+
|
|
141
|
+
// Notify clients to reload
|
|
142
|
+
for (const client of clients) {
|
|
143
|
+
try {
|
|
144
|
+
client.send(JSON.stringify({ type: 'reload', file: filename }));
|
|
145
|
+
} catch (e) {
|
|
146
|
+
clients.delete(client);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
logger.error(`Recompilation failed: ${error.message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
here i want to try and impliment the creation of a dev server using elisia bun library
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* BertUI Built-in Utilities v0.1.0 */
|
|
2
|
+
|
|
3
|
+
/* Split Text Animation */
|
|
4
|
+
.split {
|
|
5
|
+
display: inline-block;
|
|
6
|
+
position: relative;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.split::before {
|
|
11
|
+
content: attr(data-text);
|
|
12
|
+
position: absolute;
|
|
13
|
+
top: 0;
|
|
14
|
+
left: 0;
|
|
15
|
+
width: 50%;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
animation: split-left 0.6s ease-out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.split::after {
|
|
21
|
+
content: attr(data-text);
|
|
22
|
+
position: absolute;
|
|
23
|
+
top: 0;
|
|
24
|
+
right: 0;
|
|
25
|
+
width: 50%;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
animation: split-right 0.6s ease-out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@keyframes split-left {
|
|
31
|
+
from {
|
|
32
|
+
transform: translateX(-100%);
|
|
33
|
+
opacity: 0;
|
|
34
|
+
}
|
|
35
|
+
to {
|
|
36
|
+
transform: translateX(0);
|
|
37
|
+
opacity: 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@keyframes split-right {
|
|
42
|
+
from {
|
|
43
|
+
transform: translateX(100%);
|
|
44
|
+
opacity: 0;
|
|
45
|
+
}
|
|
46
|
+
to {
|
|
47
|
+
transform: translateX(0);
|
|
48
|
+
opacity: 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Move Right Animation */
|
|
53
|
+
.moveright {
|
|
54
|
+
animation: moveright 0.5s ease-out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@keyframes moveright {
|
|
58
|
+
from {
|
|
59
|
+
transform: translateX(-20px);
|
|
60
|
+
opacity: 0;
|
|
61
|
+
}
|
|
62
|
+
to {
|
|
63
|
+
transform: translateX(0);
|
|
64
|
+
opacity: 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Move Left Animation */
|
|
69
|
+
.moveleft {
|
|
70
|
+
animation: moveleft 0.5s ease-out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@keyframes moveleft {
|
|
74
|
+
from {
|
|
75
|
+
transform: translateX(20px);
|
|
76
|
+
opacity: 0;
|
|
77
|
+
}
|
|
78
|
+
to {
|
|
79
|
+
transform: translateX(0);
|
|
80
|
+
opacity: 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Fade In */
|
|
85
|
+
.fadein {
|
|
86
|
+
animation: fadein 0.5s ease-out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@keyframes fadein {
|
|
90
|
+
from {
|
|
91
|
+
opacity: 0;
|
|
92
|
+
}
|
|
93
|
+
to {
|
|
94
|
+
opacity: 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Scale In */
|
|
99
|
+
.scalein {
|
|
100
|
+
animation: scalein 0.4s ease-out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@keyframes scalein {
|
|
104
|
+
from {
|
|
105
|
+
transform: scale(0.8);
|
|
106
|
+
opacity: 0;
|
|
107
|
+
}
|
|
108
|
+
to {
|
|
109
|
+
transform: scale(1);
|
|
110
|
+
opacity: 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Bounce In */
|
|
115
|
+
.bouncein {
|
|
116
|
+
animation: bouncein 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@keyframes bouncein {
|
|
120
|
+
0% {
|
|
121
|
+
transform: scale(0);
|
|
122
|
+
opacity: 0;
|
|
123
|
+
}
|
|
124
|
+
50% {
|
|
125
|
+
transform: scale(1.1);
|
|
126
|
+
}
|
|
127
|
+
100% {
|
|
128
|
+
transform: scale(1);
|
|
129
|
+
opacity: 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Slide Up */
|
|
134
|
+
.slideup {
|
|
135
|
+
animation: slideup 0.5s ease-out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@keyframes slideup {
|
|
139
|
+
from {
|
|
140
|
+
transform: translateY(20px);
|
|
141
|
+
opacity: 0;
|
|
142
|
+
}
|
|
143
|
+
to {
|
|
144
|
+
transform: translateY(0);
|
|
145
|
+
opacity: 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Slide Down */
|
|
150
|
+
.slidedown {
|
|
151
|
+
animation: slidedown 0.5s ease-out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@keyframes slidedown {
|
|
155
|
+
from {
|
|
156
|
+
transform: translateY(-20px);
|
|
157
|
+
opacity: 0;
|
|
158
|
+
}
|
|
159
|
+
to {
|
|
160
|
+
transform: translateY(0);
|
|
161
|
+
opacity: 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Rotate In */
|
|
166
|
+
.rotatein {
|
|
167
|
+
animation: rotatein 0.6s ease-out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@keyframes rotatein {
|
|
171
|
+
from {
|
|
172
|
+
transform: rotate(-180deg) scale(0);
|
|
173
|
+
opacity: 0;
|
|
174
|
+
}
|
|
175
|
+
to {
|
|
176
|
+
transform: rotate(0) scale(1);
|
|
177
|
+
opacity: 1;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Pulse */
|
|
182
|
+
.pulse {
|
|
183
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@keyframes pulse {
|
|
187
|
+
0%, 100% {
|
|
188
|
+
transform: scale(1);
|
|
189
|
+
}
|
|
190
|
+
50% {
|
|
191
|
+
transform: scale(1.05);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Shake */
|
|
196
|
+
.shake {
|
|
197
|
+
animation: shake 0.5s ease-in-out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@keyframes shake {
|
|
201
|
+
0%, 100% {
|
|
202
|
+
transform: translateX(0);
|
|
203
|
+
}
|
|
204
|
+
10%, 30%, 50%, 70%, 90% {
|
|
205
|
+
transform: translateX(-5px);
|
|
206
|
+
}
|
|
207
|
+
20%, 40%, 60%, 80% {
|
|
208
|
+
transform: translateX(5px);
|
|
209
|
+
}
|
|
210
|
+
}
|