bertui 1.1.6 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bertui",
3
- "version": "1.1.6",
4
- "description": "Lightning-fast React dev server powered by Bun and Elysia - Now with TypeScript support!",
3
+ "version": "1.1.7",
4
+ "description": "Lightning-fast React dev server powered by Bun - Now with Rust image optimization (WASM, no Rust required for users)",
5
5
  "type": "module",
6
6
  "main": "./index.js",
7
7
  "types": "./types/index.d.ts",
@@ -11,22 +11,32 @@
11
11
  "exports": {
12
12
  ".": {
13
13
  "types": "./types/index.d.ts",
14
- "default": "./index.js"
14
+ "import": "./src/index.js",
15
+ "default": "./src/index.js"
16
+ },
17
+ "./hmr": {
18
+ "import": "./src/client/hmr-runtime.js"
19
+ },
20
+ "./image-optimizer": {
21
+ "import": "./src/image-optimizer/index.js"
15
22
  },
16
- "./styles": "./src/styles/bertui.css",
17
- "./logger": "./src/logger/logger.js",
18
23
  "./router": {
19
24
  "types": "./types/router.d.ts",
20
- "default": "./src/router/Router.jsx"
25
+ "import": "./src/router/index.js"
21
26
  },
22
27
  "./config": {
23
28
  "types": "./types/config.d.ts",
24
- "default": "./src/config/loadConfig.js"
25
- }
29
+ "import": "./src/config/index.js"
30
+ },
31
+ "./logger": {
32
+ "import": "./src/logger/logger.js"
33
+ },
34
+ "./styles": "./src/styles/bertui.css"
26
35
  },
27
36
  "files": [
28
37
  "bin",
29
38
  "src",
39
+ "dist",
30
40
  "types",
31
41
  "index.js",
32
42
  "README.md",
@@ -35,23 +45,45 @@
35
45
  "scripts": {
36
46
  "dev": "bun bin/bertui.js dev",
37
47
  "build": "bun bin/bertui.js build",
48
+ "build:wasm": "cd src/image-optimizer-rust && wasm-pack build --target web --out-dir ../../dist/image-optimizer/wasm && bun run fix:wasm",
49
+ "fix:wasm": "node scripts/fix-wasm-exports.js",
50
+ "prepublishOnly": "echo 'Skipping wasm build'",
38
51
  "test": "cd test-app && bun run dev"
39
52
  },
53
+ "dependencies": {
54
+ "elysia": "^1.0.0",
55
+ "ernest-logger": "^2.0.0",
56
+ "lightningcss": "^1.30.2",
57
+ "react-refresh": "^0.14.0"
58
+ },
59
+ "peerDependencies": {
60
+ "bun": ">=1.0.0",
61
+ "react": "^18.0.0 || ^19.0.0",
62
+ "react-dom": "^19.2.3"
63
+ },
64
+ "devDependencies": {
65
+ "@types/react": "^18.2.0",
66
+ "@types/react-dom": "^18.2.0",
67
+ "oxipng": "^1.0.1",
68
+ "wasm-pack": "^0.12.1"
69
+ },
70
+ "optionalDependencies": {
71
+ "@bertui/image-optimizer-wasm": "0.1.0",
72
+ "oxipng": "^8.0.0"
73
+ },
40
74
  "keywords": [
41
75
  "react",
42
76
  "bun",
43
- "dev-server",
44
- "vite-alternative",
45
77
  "elysia",
46
- "build-tool",
47
- "bundler",
48
- "fast",
49
78
  "hmr",
50
- "file-based-routing",
51
- "typescript",
79
+ "fast-refresh",
80
+ "image-optimization",
81
+ "rust",
82
+ "wasm",
83
+ "server-islands",
52
84
  "seo",
53
85
  "sitemap",
54
- "server-islands"
86
+ "typescript"
55
87
  ],
56
88
  "author": "Pease Ernest",
57
89
  "license": "MIT",
@@ -59,21 +91,7 @@
59
91
  "type": "git",
60
92
  "url": "https://github.com/BunElysiaReact/BERTUI.git"
61
93
  },
62
- "dependencies": {
63
- "elysia": "^1.0.0",
64
- "ernest-logger": "^2.0.0",
65
- "lightningcss": "^1.30.2"
66
- },
67
- "peerDependencies": {
68
- "react": "^18.0.0 || ^19.0.0",
69
- "react-dom": "^19.2.3",
70
- "bun": ">=1.0.0"
71
- },
72
- "devDependencies": {
73
- "@types/react": "^18.2.0",
74
- "@types/react-dom": "^18.2.0"
75
- },
76
94
  "engines": {
77
95
  "bun": ">=1.0.0"
78
96
  }
79
- }
97
+ }
@@ -1,86 +1,137 @@
1
- // bertui/src/build/image-optimizer.js - SIMPLE & STABLE
1
+ // bertui/src/build/image-optimizer.js - UPDATED WITH WASM
2
2
  import { join, extname } from 'path';
3
- import { existsSync, mkdirSync, readdirSync, cpSync } from 'fs';
3
+ import { existsSync, mkdirSync, readdirSync } from 'fs';
4
4
  import logger from '../logger/logger.js';
5
+ import { optimizeImage, hasWasm } from '../image-optimizer/index.js';
6
+ import { copyImagesSync } from '../images/index.js';
5
7
 
6
- /**
7
- * Simple, reliable image copying
8
- * No WASM, no optimization, just copy files
9
- */
8
+ export async function optimizeImages(srcDir, outDir, options = {}) {
9
+ const {
10
+ quality = 80,
11
+ webpQuality = 75,
12
+ verbose = false
13
+ } = options;
10
14
 
11
- export async function optimizeImages(srcDir, outDir) {
12
- // Alias for copyImages to maintain API
13
- logger.info(`📁 Copying from ${srcDir} to ${outDir}...`);
14
- const copied = copyImages(srcDir, outDir);
15
- return { optimized: 0, saved: 0, copied };
16
- }
17
-
18
- export async function checkOptimizationTools() {
19
- // Always return empty array to disable optimization
20
- return [];
21
- }
15
+ // Check if WASM is available
16
+ const wasmAvailable = await hasWasm();
17
+
18
+ if (wasmAvailable) {
19
+ logger.info(`🦀 Optimizing images with Rust WASM (quality: ${quality})...`);
20
+ } else {
21
+ logger.info('📋 Copying images (WASM optimizer not available)...');
22
+ }
22
23
 
23
- export function copyImages(srcDir, outDir) {
24
- // All common image formats
25
24
  const imageExtensions = [
26
25
  '.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg',
27
26
  '.avif', '.ico', '.bmp', '.tiff', '.tif'
28
27
  ];
29
28
 
29
+ let optimized = 0;
30
30
  let copied = 0;
31
31
  let skipped = 0;
32
+ let totalSaved = 0;
33
+ const results = [];
32
34
 
33
35
  if (!existsSync(srcDir)) {
34
36
  logger.warn(`⚠️ Source not found: ${srcDir}`);
35
- return 0;
37
+ return { optimized: 0, copied: 0, saved: 0, results: [] };
36
38
  }
37
39
 
38
- // Ensure output directory exists
39
40
  mkdirSync(outDir, { recursive: true });
40
41
 
41
- function processDirectory(dir, targetDir) {
42
- try {
43
- const entries = readdirSync(dir, { withFileTypes: true });
42
+ async function processDirectory(dir, targetDir) {
43
+ const entries = readdirSync(dir, { withFileTypes: true });
44
44
 
45
- for (const entry of entries) {
46
- const srcPath = join(dir, entry.name);
47
- const destPath = join(targetDir, entry.name);
45
+ for (const entry of entries) {
46
+ const srcPath = join(dir, entry.name);
47
+ const destPath = join(targetDir, entry.name);
48
48
 
49
- if (entry.isDirectory()) {
50
- // Recursively process subdirectories
51
- const subDestPath = join(targetDir, entry.name);
52
- mkdirSync(subDestPath, { recursive: true });
53
- processDirectory(srcPath, subDestPath);
54
- } else if (entry.isFile()) {
55
- const ext = extname(entry.name).toLowerCase();
56
-
57
- if (imageExtensions.includes(ext)) {
58
- try {
59
- cpSync(srcPath, destPath);
49
+ if (entry.isDirectory()) {
50
+ const subDestPath = join(targetDir, entry.name);
51
+ mkdirSync(subDestPath, { recursive: true });
52
+ await processDirectory(srcPath, subDestPath);
53
+ } else if (entry.isFile()) {
54
+ const ext = extname(entry.name).toLowerCase();
55
+
56
+ if (imageExtensions.includes(ext)) {
57
+ try {
58
+ const file = Bun.file(srcPath);
59
+ const buffer = await file.arrayBuffer();
60
+ const originalSize = buffer.byteLength;
61
+
62
+ // Try WASM optimization first, fallback to copy
63
+ if (wasmAvailable && ['.png', '.jpg', '.jpeg', '.webp'].includes(ext)) {
64
+ const format = ext.slice(1);
65
+ const result = await optimizeImage(buffer, {
66
+ format,
67
+ quality,
68
+ webpQuality
69
+ });
70
+
71
+ await Bun.write(destPath, new Uint8Array(result.data));
72
+
73
+ const saved = originalSize - result.optimized_size;
74
+ totalSaved += saved;
75
+ optimized++;
76
+
77
+ results.push({
78
+ file: entry.name,
79
+ original: formatBytes(originalSize),
80
+ optimized: formatBytes(result.optimized_size),
81
+ saved: formatBytes(saved),
82
+ percent: result.savings_percent.toFixed(1) + '%'
83
+ });
84
+ } else {
85
+ // Just copy unsupported formats
86
+ await Bun.write(destPath, file);
60
87
  copied++;
61
- } catch (error) {
62
- logger.warn(` Failed to copy ${entry.name}: ${error.message}`);
63
- skipped++;
88
+ results.push({
89
+ file: entry.name,
90
+ status: 'copied'
91
+ });
64
92
  }
65
- } else {
66
- skipped++;
93
+ } catch (error) {
94
+ logger.warn(` Failed to process ${entry.name}: ${error.message}`);
95
+ // Fallback: copy original
96
+ await Bun.write(destPath, Bun.file(srcPath));
97
+ copied++;
67
98
  }
99
+ } else {
100
+ // Copy non-image files
101
+ await Bun.write(destPath, Bun.file(srcPath));
102
+ skipped++;
68
103
  }
69
104
  }
70
- } catch (error) {
71
- logger.error(`Error processing ${dir}: ${error.message}`);
72
105
  }
73
106
  }
74
107
 
75
- processDirectory(srcDir, outDir);
108
+ await processDirectory(srcDir, outDir);
76
109
 
77
- if (copied > 0) {
78
- logger.success(`✅ Copied ${copied} image(s) to ${outDir}`);
110
+ // Show summary
111
+ if (optimized > 0) {
112
+ logger.success(`✅ Optimized ${optimized} images with Rust WASM`);
113
+ logger.table(results.slice(0, 10));
114
+ if (results.length > 10) {
115
+ logger.info(` ... and ${results.length - 10} more images`);
116
+ }
117
+ logger.info(`📊 Total saved: ${formatBytes(totalSaved)}`);
79
118
  }
80
119
 
81
- if (skipped > 0) {
82
- logger.info(`📝 Skipped ${skipped} non-image file(s)`);
120
+ if (copied > 0) {
121
+ logger.info(`📋 Copied ${copied} images (fallback)`);
83
122
  }
84
123
 
85
- return copied;
124
+ return { optimized, copied, saved: totalSaved, results };
125
+ }
126
+
127
+ export function copyImages(srcDir, outDir) {
128
+ return copyImagesSync(srcDir, outDir);
129
+ }
130
+
131
+ function formatBytes(bytes) {
132
+ if (bytes === 0) return '0 B';
133
+ const k = 1024;
134
+ const sizes = ['B', 'KB', 'MB', 'GB'];
135
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
136
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
86
137
  }
@@ -0,0 +1,72 @@
1
+ // bertui/src/client/fast-refresh.js
2
+ // React Fast Refresh integration
3
+
4
+ import RefreshRuntime from 'react-refresh';
5
+
6
+ // Inject into global scope
7
+ if (typeof window !== 'undefined') {
8
+ // Setup Fast Refresh globals
9
+ window.$RefreshReg$ = (type, id) => {
10
+ RefreshRuntime.register(type, id);
11
+ };
12
+
13
+ window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
14
+
15
+ // Store runtime in global
16
+ window.$RefreshRuntime$ = RefreshRuntime;
17
+
18
+ // Inject into global hook
19
+ if (!window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
20
+ window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
21
+ supportsFiber: true,
22
+ inject: (fiber) => {},
23
+ onCommitFiberRoot: (rendererId, root) => {},
24
+ onCommitFiberUnmount: () => {}
25
+ };
26
+ }
27
+
28
+ // Setup refresh handler
29
+ RefreshRuntime.injectIntoGlobalHook(window);
30
+
31
+ // Create queue for batched updates
32
+ let updateQueue = [];
33
+ let scheduled = false;
34
+
35
+ const scheduleUpdate = () => {
36
+ if (scheduled) return;
37
+ scheduled = true;
38
+
39
+ queueMicrotask(() => {
40
+ scheduled = false;
41
+ const queue = updateQueue;
42
+ updateQueue = [];
43
+
44
+ if (queue.length > 0 && window.$RefreshRuntime$) {
45
+ window.$RefreshRuntime$.performReactRefresh();
46
+ }
47
+ });
48
+ };
49
+
50
+ // Listen for HMR updates
51
+ window.addEventListener('hmr-module-updated', (event) => {
52
+ const { moduleId } = event.detail;
53
+ updateQueue.push(moduleId);
54
+ scheduleUpdate();
55
+ });
56
+
57
+ console.log('%c⚡ React Fast Refresh enabled', 'color: #3b82f6; font-weight: bold');
58
+ }
59
+
60
+ // Export for module usage
61
+ export const setupFastRefresh = () => {
62
+ if (typeof window !== 'undefined' && window.$RefreshRuntime$) {
63
+ return window.$RefreshRuntime$;
64
+ }
65
+ return null;
66
+ };
67
+
68
+ export const performReactRefresh = () => {
69
+ if (typeof window !== 'undefined' && window.$RefreshRuntime$) {
70
+ window.$RefreshRuntime$.performReactRefresh();
71
+ }
72
+ };
@@ -0,0 +1,59 @@
1
+ // bertui/src/client/hmr-runtime.js
2
+ // ONE SOLUTION - Fast Refresh HMR
3
+
4
+ (function(global) {
5
+ let socket = null;
6
+ let reconnectTimer = null;
7
+
8
+ function connect() {
9
+ const protocol = global.location.protocol === 'https:' ? 'wss:' : 'ws:';
10
+ socket = new WebSocket(`${protocol}//${global.location.host}/__hmr`);
11
+
12
+ socket.onopen = () => {
13
+ console.log('%c🔥 HMR connected', 'color: #10b981; font-weight: bold');
14
+ };
15
+
16
+ socket.onmessage = async (event) => {
17
+ const data = JSON.parse(event.data);
18
+
19
+ if (data.type === 'hmr-update') {
20
+ console.log(`%c🔥 HMR: ${data.module} (${data.time}ms)`, 'color: #10b981');
21
+
22
+ try {
23
+ const url = new URL(data.module, global.location.origin);
24
+ url.searchParams.set('t', Date.now());
25
+ await import(url.toString());
26
+
27
+ if (global.$RefreshRuntime$) {
28
+ global.$RefreshRuntime$.performReactRefresh();
29
+ }
30
+ } catch (err) {
31
+ console.error('HMR update failed:', err);
32
+ global.location.reload();
33
+ }
34
+ }
35
+
36
+ if (data.type === 'full-reload') {
37
+ global.location.reload();
38
+ }
39
+ };
40
+
41
+ socket.onclose = () => {
42
+ console.log('%c⚠️ HMR disconnected, reconnecting...', 'color: #f59e0b');
43
+ reconnectTimer = setTimeout(connect, 2000);
44
+ };
45
+ }
46
+
47
+ if (global.document) {
48
+ global.addEventListener('load', connect);
49
+ }
50
+
51
+ // Fast Refresh setup
52
+ if (typeof window !== 'undefined') {
53
+ window.$RefreshReg$ = (type, id) => {};
54
+ window.$RefreshSig$ = () => (type) => type;
55
+ }
56
+
57
+ })(typeof window !== 'undefined' ? window : global);
58
+
59
+ export const hmr = { connect: () => {} };
@@ -0,0 +1,25 @@
1
+ // bertui/src/compiler/index.js - NEW FILE (PURE, NO SERVER)
2
+ export { compileProject } from '../client/compiler.js';
3
+ export { compileForBuild } from '../build/compiler/index.js';
4
+ export { discoverRoutes } from '../build/compiler/route-discoverer.js';
5
+ export { validateServerIsland } from '../build/server-island-validator.js';
6
+
7
+ // PURE JSX→JS TRANSFORMATION
8
+ export async function transformJSX(sourceCode, options = {}) {
9
+ const transpiler = new Bun.Transpiler({
10
+ loader: options.loader || 'tsx',
11
+ target: 'browser',
12
+ define: {
13
+ 'process.env.NODE_ENV': JSON.stringify(options.env || 'development')
14
+ }
15
+ });
16
+ return await transpiler.transform(sourceCode);
17
+ }
18
+
19
+ // Export everything from this directory
20
+ export { transformJSX, transformJSXSync, containsJSX, removeCSSImports, removeDotenvImports, fixRelativeImports } from './transform.js';
21
+ export { generateRouterCode } from './router-generator-pure.js';
22
+ // Re-export existing
23
+ export { compileProject } from '../client/compiler.js';
24
+ export { compileForBuild } from '../build/compiler/index.js';
25
+ export { discoverRoutes } from '../build/compiler/route-discoverer.js';
@@ -0,0 +1,104 @@
1
+ // bertui/src/compiler/router-generator-pure.js - NEW FILE
2
+ // PURE function - no file system, no server
3
+
4
+ export function generateRouterCode(routes) {
5
+ const imports = routes.map((route, i) => {
6
+ const componentName = `Page${i}`;
7
+ const importPath = route.importPath || `./pages/${route.file.replace(/\.(jsx|tsx|ts)$/, '.js')}`;
8
+ return `import ${componentName} from '${importPath}';`;
9
+ }).join('\n');
10
+
11
+ const routeConfigs = routes.map((route, i) => {
12
+ const componentName = `Page${i}`;
13
+ return ` { path: '${route.route}', component: ${componentName}, type: '${route.type}' }`;
14
+ }).join(',\n');
15
+
16
+ return `import React, { useState, useEffect, createContext, useContext } from 'react';
17
+
18
+ const RouterContext = createContext(null);
19
+
20
+ export function useRouter() {
21
+ const context = useContext(RouterContext);
22
+ if (!context) throw new Error('useRouter must be used within a Router');
23
+ return context;
24
+ }
25
+
26
+ export function Router({ routes }) {
27
+ const [currentRoute, setCurrentRoute] = useState(null);
28
+ const [params, setParams] = useState({});
29
+
30
+ useEffect(() => {
31
+ matchAndSetRoute(window.location.pathname);
32
+ const handlePopState = () => matchAndSetRoute(window.location.pathname);
33
+ window.addEventListener('popstate', handlePopState);
34
+ return () => window.removeEventListener('popstate', handlePopState);
35
+ }, [routes]);
36
+
37
+ function matchAndSetRoute(pathname) {
38
+ // Static routes
39
+ for (const route of routes) {
40
+ if (route.type === 'static' && route.path === pathname) {
41
+ setCurrentRoute(route);
42
+ setParams({});
43
+ return;
44
+ }
45
+ }
46
+ // Dynamic routes
47
+ for (const route of routes) {
48
+ if (route.type === 'dynamic') {
49
+ const pattern = route.path.replace(/\\[([^\\]]+)\\]/g, '([^/]+)');
50
+ const regex = new RegExp('^' + pattern + '$');
51
+ const match = pathname.match(regex);
52
+ if (match) {
53
+ const paramNames = [...route.path.matchAll(/\\[([^\\]]+)\\]/g)].map(m => m[1]);
54
+ const extractedParams = {};
55
+ paramNames.forEach((name, i) => { extractedParams[name] = match[i + 1]; });
56
+ setCurrentRoute(route);
57
+ setParams(extractedParams);
58
+ return;
59
+ }
60
+ }
61
+ }
62
+ setCurrentRoute(null);
63
+ setParams({});
64
+ }
65
+
66
+ function navigate(path) {
67
+ window.history.pushState({}, '', path);
68
+ matchAndSetRoute(path);
69
+ }
70
+
71
+ const Component = currentRoute?.component;
72
+ return React.createElement(
73
+ RouterContext.Provider,
74
+ { value: { currentRoute, params, navigate, pathname: window.location.pathname } },
75
+ Component ? React.createElement(Component, { params }) : React.createElement(NotFound)
76
+ );
77
+ }
78
+
79
+ export function Link({ to, children, ...props }) {
80
+ const { navigate } = useRouter();
81
+ return React.createElement('a', {
82
+ href: to,
83
+ onClick: (e) => { e.preventDefault(); navigate(to); },
84
+ ...props
85
+ }, children);
86
+ }
87
+
88
+ function NotFound() {
89
+ return React.createElement('div', {
90
+ style: { display: 'flex', flexDirection: 'column', alignItems: 'center',
91
+ justifyContent: 'center', minHeight: '100vh', fontFamily: 'system-ui' }
92
+ },
93
+ React.createElement('h1', { style: { fontSize: '6rem', margin: 0 } }, '404'),
94
+ React.createElement('p', { style: { fontSize: '1.5rem', color: '#666' } }, 'Page not found'),
95
+ React.createElement('a', { href: '/', style: { color: '#10b981', textDecoration: 'none' } }, 'Go home')
96
+ );
97
+ }
98
+
99
+ ${imports}
100
+
101
+ export const routes = [
102
+ ${routeConfigs}
103
+ ];`;
104
+ }