bertui 1.2.8 → 2.0.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/README.md +45 -196
- package/TYPES_PATCH.md +17 -0
- package/bin/bertui.js +2 -7
- package/package.json +32 -98
- package/src/config.ts +4 -0
- package/src/index.ts +32 -0
- package/src/optional.ts +49 -0
- package/src/router.ts +3 -0
- package/tsconfig.json +29 -0
- package/LICENSE +0 -21
- package/index.js +0 -103
- package/src/analyzer/index.js +0 -370
- package/src/build/compiler/file-transpiler.js +0 -216
- package/src/build/compiler/index.js +0 -31
- package/src/build/compiler/route-discoverer.js +0 -49
- package/src/build/compiler/router-generator.js +0 -105
- package/src/build/css-builder.js +0 -81
- package/src/build/generators/html-generator.js +0 -151
- package/src/build/generators/robots-generator.js +0 -58
- package/src/build/generators/sitemap-generator.js +0 -63
- package/src/build/image-optimizer.js +0 -137
- package/src/build/processors/asset-processor.js +0 -19
- package/src/build/processors/css-builder.js +0 -142
- package/src/build/server-island-validator.js +0 -12
- package/src/build.js +0 -266
- package/src/cli.js +0 -131
- package/src/client/compiler.js +0 -522
- package/src/client/fast-refresh.js +0 -72
- package/src/client/hmr-runtime.js +0 -59
- package/src/compiler/index.js +0 -25
- package/src/compiler/router-generator-pure.js +0 -104
- package/src/compiler/transform.js +0 -149
- package/src/config/defaultConfig.js +0 -37
- package/src/config/index.js +0 -2
- package/src/config/loadConfig.js +0 -64
- package/src/config/og-image.png +0 -0
- package/src/css/index.js +0 -46
- package/src/css/processor.js +0 -172
- package/src/dev.js +0 -68
- package/src/hydration/index.js +0 -151
- package/src/image-optimizer/index.js +0 -103
- package/src/images/index.js +0 -102
- package/src/images/processor.js +0 -169
- package/src/layouts/index.js +0 -165
- package/src/loading/index.js +0 -210
- package/src/logger/logger.js +0 -320
- package/src/logger/notes.md +0 -20
- package/src/middleware/index.js +0 -182
- package/src/router/Router.js +0 -150
- package/src/router/SSRRouter.js +0 -156
- package/src/router/index.js +0 -3
- package/src/scaffolder/index.js +0 -310
- package/src/serve.js +0 -193
- package/src/server/dev-handler.js +0 -195
- package/src/server/dev-server-utils.js +0 -406
- package/src/server/dev-server.js +0 -15
- package/src/server/hmr-handler.js +0 -148
- package/src/server/index.js +0 -3
- package/src/server/notes.md +0 -1
- package/src/server/request-handler.js +0 -36
- package/src/server-islands/extractor.js +0 -198
- package/src/server-islands/index.js +0 -59
- package/src/styles/bertui.css +0 -210
- package/src/utils/cache.js +0 -297
- package/src/utils/env.js +0 -87
- package/src/utils/importhow.js +0 -52
- package/src/utils/index.js +0 -11
- package/src/utils/meta-extractor.js +0 -127
- package/types/bin/bertui.d.ts +0 -3
- package/types/bin/bertui.d.ts.map +0 -1
- package/types/error-overlay.d.ts +0 -2
- package/types/error-overlay.d.ts.map +0 -1
- package/types/index.d.ts +0 -26
- package/types/index.d.ts.map +0 -1
- package/types/scripts/fix-wasm-exports.d.ts +0 -2
- package/types/scripts/fix-wasm-exports.d.ts.map +0 -1
- package/types/src/analyzer/index.d.ts +0 -8
- package/types/src/analyzer/index.d.ts.map +0 -1
- package/types/src/build/compiler/file-transpiler.d.ts +0 -5
- package/types/src/build/compiler/file-transpiler.d.ts.map +0 -1
- package/types/src/build/compiler/index.d.ts +0 -12
- package/types/src/build/compiler/index.d.ts.map +0 -1
- package/types/src/build/compiler/route-discoverer.d.ts +0 -2
- package/types/src/build/compiler/route-discoverer.d.ts.map +0 -1
- package/types/src/build/compiler/router-generator.d.ts +0 -2
- package/types/src/build/compiler/router-generator.d.ts.map +0 -1
- package/types/src/build/css-builder.d.ts +0 -18
- package/types/src/build/css-builder.d.ts.map +0 -1
- package/types/src/build/generators/html-generator.d.ts +0 -2
- package/types/src/build/generators/html-generator.d.ts.map +0 -1
- package/types/src/build/generators/robots-generator.d.ts +0 -11
- package/types/src/build/generators/robots-generator.d.ts.map +0 -1
- package/types/src/build/generators/sitemap-generator.d.ts +0 -5
- package/types/src/build/generators/sitemap-generator.d.ts.map +0 -1
- package/types/src/build/image-optimizer.d.ts +0 -11
- package/types/src/build/image-optimizer.d.ts.map +0 -1
- package/types/src/build/processors/asset-processor.d.ts +0 -2
- package/types/src/build/processors/asset-processor.d.ts.map +0 -1
- package/types/src/build/processors/css-builder.d.ts +0 -2
- package/types/src/build/processors/css-builder.d.ts.map +0 -1
- package/types/src/build/server-island-validator.d.ts +0 -27
- package/types/src/build/server-island-validator.d.ts.map +0 -1
- package/types/src/build.d.ts +0 -5
- package/types/src/build.d.ts.map +0 -1
- package/types/src/cli.d.ts +0 -2
- package/types/src/cli.d.ts.map +0 -1
- package/types/src/client/compiler.d.ts +0 -16
- package/types/src/client/compiler.d.ts.map +0 -1
- package/types/src/client/fast-refresh.d.ts +0 -3
- package/types/src/client/fast-refresh.d.ts.map +0 -1
- package/types/src/client/hmr-runtime.d.ts +0 -4
- package/types/src/client/hmr-runtime.d.ts.map +0 -1
- package/types/src/compiler/index.d.ts +0 -8
- package/types/src/compiler/index.d.ts.map +0 -1
- package/types/src/compiler/router-generator-pure.d.ts +0 -2
- package/types/src/compiler/router-generator-pure.d.ts.map +0 -1
- package/types/src/compiler/transform.d.ts +0 -36
- package/types/src/compiler/transform.d.ts.map +0 -1
- package/types/src/config/defaultConfig.d.ts +0 -26
- package/types/src/config/defaultConfig.d.ts.map +0 -1
- package/types/src/config/index.d.ts +0 -3
- package/types/src/config/index.d.ts.map +0 -1
- package/types/src/config/loadConfig.d.ts +0 -2
- package/types/src/config/loadConfig.d.ts.map +0 -1
- package/types/src/css/index.d.ts +0 -6
- package/types/src/css/index.d.ts.map +0 -1
- package/types/src/css/processor.d.ts +0 -23
- package/types/src/css/processor.d.ts.map +0 -1
- package/types/src/dev.d.ts +0 -2
- package/types/src/dev.d.ts.map +0 -1
- package/types/src/hydration/index.d.ts +0 -33
- package/types/src/hydration/index.d.ts.map +0 -1
- package/types/src/image-optimizer/index.d.ts +0 -24
- package/types/src/image-optimizer/index.d.ts.map +0 -1
- package/types/src/images/index.d.ts +0 -12
- package/types/src/images/index.d.ts.map +0 -1
- package/types/src/images/processor.d.ts +0 -30
- package/types/src/images/processor.d.ts.map +0 -1
- package/types/src/layouts/index.d.ts +0 -28
- package/types/src/layouts/index.d.ts.map +0 -1
- package/types/src/loading/index.d.ts +0 -28
- package/types/src/loading/index.d.ts.map +0 -1
- package/types/src/logger/logger.d.ts +0 -30
- package/types/src/logger/logger.d.ts.map +0 -1
- package/types/src/middleware/index.d.ts +0 -61
- package/types/src/middleware/index.d.ts.map +0 -1
- package/types/src/router/Router.d.ts +0 -16
- package/types/src/router/Router.d.ts.map +0 -1
- package/types/src/router/SSRRouter.d.ts +0 -20
- package/types/src/router/SSRRouter.d.ts.map +0 -1
- package/types/src/router/index.d.ts +0 -3
- package/types/src/router/index.d.ts.map +0 -1
- package/types/src/scaffolder/index.d.ts +0 -14
- package/types/src/scaffolder/index.d.ts.map +0 -1
- package/types/src/serve.d.ts +0 -3
- package/types/src/serve.d.ts.map +0 -1
- package/types/src/server/dev-handler.d.ts +0 -13
- package/types/src/server/dev-handler.d.ts.map +0 -1
- package/types/src/server/dev-server-utils.d.ts +0 -6
- package/types/src/server/dev-server-utils.d.ts.map +0 -1
- package/types/src/server/dev-server.d.ts +0 -18
- package/types/src/server/dev-server.d.ts.map +0 -1
- package/types/src/server/hmr-handler.d.ts +0 -19
- package/types/src/server/hmr-handler.d.ts.map +0 -1
- package/types/src/server/index.d.ts +0 -4
- package/types/src/server/index.d.ts.map +0 -1
- package/types/src/server/request-handler.d.ts +0 -19
- package/types/src/server/request-handler.d.ts.map +0 -1
- package/types/src/server-islands/extractor.d.ts +0 -16
- package/types/src/server-islands/extractor.d.ts.map +0 -1
- package/types/src/server-islands/index.d.ts +0 -3
- package/types/src/server-islands/index.d.ts.map +0 -1
- package/types/src/utils/cache.d.ts +0 -52
- package/types/src/utils/cache.d.ts.map +0 -1
- package/types/src/utils/env.d.ts +0 -20
- package/types/src/utils/env.d.ts.map +0 -1
- package/types/src/utils/importhow.d.ts +0 -15
- package/types/src/utils/importhow.d.ts.map +0 -1
- package/types/src/utils/index.d.ts +0 -3
- package/types/src/utils/index.d.ts.map +0 -1
- package/types/src/utils/meta-extractor.d.ts +0 -13
- package/types/src/utils/meta-extractor.d.ts.map +0 -1
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// bertui/src/build/compiler/index.js
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
|
-
import logger from '../../logger/logger.js';
|
|
5
|
-
import { discoverRoutes } from './route-discoverer.js';
|
|
6
|
-
import { compileBuildDirectory } from './file-transpiler.js';
|
|
7
|
-
import { generateBuildRouter } from './router-generator.js';
|
|
8
|
-
|
|
9
|
-
export async function compileForBuild(root, buildDir, envVars, config = {}) {
|
|
10
|
-
const srcDir = join(root, 'src');
|
|
11
|
-
const pagesDir = join(srcDir, 'pages');
|
|
12
|
-
|
|
13
|
-
if (!existsSync(srcDir)) {
|
|
14
|
-
throw new Error('src/ directory not found!');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const importhow = config.importhow || {};
|
|
18
|
-
let routes = [];
|
|
19
|
-
|
|
20
|
-
if (existsSync(pagesDir)) {
|
|
21
|
-
routes = await discoverRoutes(pagesDir);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
await compileBuildDirectory(srcDir, buildDir, root, envVars, importhow);
|
|
25
|
-
|
|
26
|
-
if (routes.length > 0) {
|
|
27
|
-
await generateBuildRouter(routes, buildDir);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return { routes };
|
|
31
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// bertui/src/build/compiler/route-discoverer.js
|
|
2
|
-
import { join, extname } from 'path';
|
|
3
|
-
import { readdirSync, statSync } from 'fs';
|
|
4
|
-
|
|
5
|
-
export async function discoverRoutes(pagesDir) {
|
|
6
|
-
const routes = [];
|
|
7
|
-
|
|
8
|
-
async function scanDirectory(dir, basePath = '') {
|
|
9
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
10
|
-
|
|
11
|
-
for (const entry of entries) {
|
|
12
|
-
const fullPath = join(dir, entry.name);
|
|
13
|
-
const relativePath = join(basePath, entry.name);
|
|
14
|
-
|
|
15
|
-
if (entry.isDirectory()) {
|
|
16
|
-
await scanDirectory(fullPath, relativePath);
|
|
17
|
-
} else if (entry.isFile()) {
|
|
18
|
-
const ext = extname(entry.name);
|
|
19
|
-
if (ext === '.css') continue;
|
|
20
|
-
|
|
21
|
-
if (['.jsx', '.tsx', '.js', '.ts'].includes(ext)) {
|
|
22
|
-
const fileName = entry.name.replace(ext, '');
|
|
23
|
-
|
|
24
|
-
// Only loading is reserved - index is a valid route (becomes /)
|
|
25
|
-
if (fileName === 'loading') continue;
|
|
26
|
-
|
|
27
|
-
let route = '/' + relativePath.replace(/\\/g, '/').replace(ext, '');
|
|
28
|
-
if (fileName === 'index') {
|
|
29
|
-
route = route.replace('/index', '') || '/';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const isDynamic = fileName.includes('[') && fileName.includes(']');
|
|
33
|
-
|
|
34
|
-
routes.push({
|
|
35
|
-
route: route === '' ? '/' : route,
|
|
36
|
-
file: relativePath.replace(/\\/g, '/'),
|
|
37
|
-
path: fullPath,
|
|
38
|
-
type: isDynamic ? 'dynamic' : 'static'
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
await scanDirectory(pagesDir);
|
|
46
|
-
routes.sort((a, b) => a.type === b.type ? a.route.localeCompare(b.route) : a.type === 'static' ? -1 : 1);
|
|
47
|
-
|
|
48
|
-
return routes;
|
|
49
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// bertui/src/build/compiler/router-generator.js
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
|
|
4
|
-
export async function generateBuildRouter(routes, buildDir) {
|
|
5
|
-
const imports = routes.map((route, i) => {
|
|
6
|
-
const componentName = `Page${i}`;
|
|
7
|
-
const 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
|
-
const routerCode = `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
|
-
for (const route of routes) {
|
|
39
|
-
if (route.type === 'static' && route.path === pathname) {
|
|
40
|
-
setCurrentRoute(route);
|
|
41
|
-
setParams({});
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
for (const route of routes) {
|
|
46
|
-
if (route.type === 'dynamic') {
|
|
47
|
-
const pattern = route.path.replace(/\\[([^\\]]+)\\]/g, '([^/]+)');
|
|
48
|
-
const regex = new RegExp('^' + pattern + '$');
|
|
49
|
-
const match = pathname.match(regex);
|
|
50
|
-
if (match) {
|
|
51
|
-
const paramNames = [...route.path.matchAll(/\\[([^\\]]+)\\]/g)].map(m => m[1]);
|
|
52
|
-
const extractedParams = {};
|
|
53
|
-
paramNames.forEach((name, i) => { extractedParams[name] = match[i + 1]; });
|
|
54
|
-
setCurrentRoute(route);
|
|
55
|
-
setParams(extractedParams);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
setCurrentRoute(null);
|
|
61
|
-
setParams({});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function navigate(path) {
|
|
65
|
-
window.history.pushState({}, '', path);
|
|
66
|
-
matchAndSetRoute(path);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const Component = currentRoute?.component;
|
|
70
|
-
return React.createElement(
|
|
71
|
-
RouterContext.Provider,
|
|
72
|
-
{ value: { currentRoute, params, navigate, pathname: window.location.pathname } },
|
|
73
|
-
Component ? React.createElement(Component, { params }) : React.createElement(NotFound)
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function Link({ to, children, ...props }) {
|
|
78
|
-
const { navigate } = useRouter();
|
|
79
|
-
return React.createElement('a', {
|
|
80
|
-
href: to,
|
|
81
|
-
onClick: (e) => { e.preventDefault(); navigate(to); },
|
|
82
|
-
...props
|
|
83
|
-
}, children);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function NotFound() {
|
|
87
|
-
return React.createElement('div', {
|
|
88
|
-
style: { display: 'flex', flexDirection: 'column', alignItems: 'center',
|
|
89
|
-
justifyContent: 'center', minHeight: '100vh', fontFamily: 'system-ui' }
|
|
90
|
-
},
|
|
91
|
-
React.createElement('h1', { style: { fontSize: '6rem', margin: 0 } }, '404'),
|
|
92
|
-
React.createElement('p', { style: { fontSize: '1.5rem', color: '#666' } }, 'Page not found'),
|
|
93
|
-
React.createElement('a', { href: '/', style: { color: '#10b981', textDecoration: 'none' } }, 'Go home')
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
${imports}
|
|
98
|
-
|
|
99
|
-
export const routes = [
|
|
100
|
-
${routeConfigs}
|
|
101
|
-
];
|
|
102
|
-
`;
|
|
103
|
-
|
|
104
|
-
await Bun.write(join(buildDir, 'router.js'), routerCode);
|
|
105
|
-
}
|
package/src/build/css-builder.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { join } from 'path';
|
|
2
|
-
import { existsSync, mkdirSync } from 'fs';
|
|
3
|
-
import { transform } from 'lightningcss';
|
|
4
|
-
import logger from '../logger/logger.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Build and minify CSS for production using Lightning CSS
|
|
8
|
-
* @param {string} srcPath - Source CSS file path
|
|
9
|
-
* @param {string} destPath - Destination CSS file path
|
|
10
|
-
*/
|
|
11
|
-
export async function buildCSS(srcPath, destPath) {
|
|
12
|
-
try {
|
|
13
|
-
logger.info(`Processing CSS: ${srcPath.split('/').pop()}`);
|
|
14
|
-
|
|
15
|
-
// Ensure destination directory exists
|
|
16
|
-
const destDir = join(destPath, '..');
|
|
17
|
-
if (!existsSync(destDir)) {
|
|
18
|
-
mkdirSync(destDir, { recursive: true });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Read source CSS
|
|
22
|
-
const css = await Bun.file(srcPath).text();
|
|
23
|
-
const originalSize = Buffer.byteLength(css);
|
|
24
|
-
|
|
25
|
-
// Transform with Lightning CSS (blazing fast)
|
|
26
|
-
const { code } = transform({
|
|
27
|
-
filename: srcPath,
|
|
28
|
-
code: Buffer.from(css),
|
|
29
|
-
minify: true,
|
|
30
|
-
sourceMap: false,
|
|
31
|
-
targets: {
|
|
32
|
-
// Support last 2 versions of major browsers
|
|
33
|
-
chrome: 90 << 16,
|
|
34
|
-
firefox: 88 << 16,
|
|
35
|
-
safari: 14 << 16,
|
|
36
|
-
edge: 90 << 16
|
|
37
|
-
},
|
|
38
|
-
drafts: {
|
|
39
|
-
nesting: true // Enable CSS nesting
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Write minified CSS
|
|
44
|
-
await Bun.write(destPath, code);
|
|
45
|
-
|
|
46
|
-
// Calculate size reduction
|
|
47
|
-
const minifiedSize = code.length;
|
|
48
|
-
const originalKB = (originalSize / 1024).toFixed(2);
|
|
49
|
-
const minifiedKB = (minifiedSize / 1024).toFixed(2);
|
|
50
|
-
const reduction = ((1 - minifiedSize / originalSize) * 100).toFixed(1);
|
|
51
|
-
|
|
52
|
-
logger.success(`CSS minified: ${originalKB}KB → ${minifiedKB}KB (-${reduction}%)`);
|
|
53
|
-
|
|
54
|
-
return { success: true, size: minifiedKB };
|
|
55
|
-
} catch (error) {
|
|
56
|
-
logger.error(`CSS build failed: ${error.message}`);
|
|
57
|
-
throw error;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Copy CSS without minification (for dev)
|
|
63
|
-
* @param {string} srcPath - Source CSS file path
|
|
64
|
-
* @param {string} destPath - Destination CSS file path
|
|
65
|
-
*/
|
|
66
|
-
export async function copyCSS(srcPath, destPath) {
|
|
67
|
-
try {
|
|
68
|
-
const destDir = join(destPath, '..');
|
|
69
|
-
if (!existsSync(destDir)) {
|
|
70
|
-
mkdirSync(destDir, { recursive: true });
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
await Bun.write(destPath, Bun.file(srcPath));
|
|
74
|
-
logger.info('CSS copied for development');
|
|
75
|
-
|
|
76
|
-
return { success: true };
|
|
77
|
-
} catch (error) {
|
|
78
|
-
logger.error(`CSS copy failed: ${error.message}`);
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
// bertui/src/build/generators/html-generator.js
|
|
2
|
-
import { join, relative } from 'path';
|
|
3
|
-
import { mkdirSync, existsSync, cpSync } from 'fs';
|
|
4
|
-
import logger from '../../logger/logger.js';
|
|
5
|
-
import { extractMetaFromSource } from '../../utils/meta-extractor.js';
|
|
6
|
-
|
|
7
|
-
export async function generateProductionHTML(root, outDir, buildResult, routes, config) {
|
|
8
|
-
const mainBundle = buildResult.outputs.find(o =>
|
|
9
|
-
o.path.includes('main') && o.kind === 'entry-point'
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
if (!mainBundle) {
|
|
13
|
-
logger.error('Could not find main bundle');
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const bundlePath = relative(outDir, mainBundle.path).replace(/\\/g, '/');
|
|
18
|
-
const defaultMeta = config.meta || {};
|
|
19
|
-
const bertuiPackages = await copyBertuiPackagesToProduction(root, outDir);
|
|
20
|
-
|
|
21
|
-
logger.info(`Generating HTML for ${routes.length} routes...`);
|
|
22
|
-
|
|
23
|
-
for (const route of routes) {
|
|
24
|
-
await processSingleRoute(route, config, defaultMeta, bundlePath, outDir, bertuiPackages);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
logger.success(`HTML generation complete for ${routes.length} routes`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function copyBertuiPackagesToProduction(root, outDir) {
|
|
31
|
-
const nodeModulesDir = join(root, 'node_modules');
|
|
32
|
-
const packages = { bertuiIcons: false, bertuiAnimate: false, elysiaEden: false };
|
|
33
|
-
|
|
34
|
-
if (!existsSync(nodeModulesDir)) return packages;
|
|
35
|
-
|
|
36
|
-
const bertuiIconsSource = join(nodeModulesDir, 'bertui-icons');
|
|
37
|
-
if (existsSync(bertuiIconsSource)) {
|
|
38
|
-
try {
|
|
39
|
-
const bertuiIconsDest = join(outDir, 'node_modules', 'bertui-icons');
|
|
40
|
-
mkdirSync(join(outDir, 'node_modules'), { recursive: true });
|
|
41
|
-
cpSync(bertuiIconsSource, bertuiIconsDest, { recursive: true });
|
|
42
|
-
packages.bertuiIcons = true;
|
|
43
|
-
} catch (error) {
|
|
44
|
-
logger.error(`Failed to copy bertui-icons: ${error.message}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const bertuiAnimateSource = join(nodeModulesDir, 'bertui-animate', 'dist');
|
|
49
|
-
if (existsSync(bertuiAnimateSource)) {
|
|
50
|
-
try {
|
|
51
|
-
const bertuiAnimateDest = join(outDir, 'css');
|
|
52
|
-
mkdirSync(bertuiAnimateDest, { recursive: true });
|
|
53
|
-
const minCSSPath = join(bertuiAnimateSource, 'bertui-animate.min.css');
|
|
54
|
-
if (existsSync(minCSSPath)) {
|
|
55
|
-
cpSync(minCSSPath, join(bertuiAnimateDest, 'bertui-animate.min.css'));
|
|
56
|
-
packages.bertuiAnimate = true;
|
|
57
|
-
}
|
|
58
|
-
} catch (error) {
|
|
59
|
-
logger.error(`Failed to copy bertui-animate: ${error.message}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const elysiaEdenSource = join(nodeModulesDir, '@elysiajs', 'eden');
|
|
64
|
-
if (existsSync(elysiaEdenSource)) {
|
|
65
|
-
try {
|
|
66
|
-
const elysiaEdenDest = join(outDir, 'node_modules', '@elysiajs', 'eden');
|
|
67
|
-
mkdirSync(join(outDir, 'node_modules', '@elysiajs'), { recursive: true });
|
|
68
|
-
cpSync(elysiaEdenSource, elysiaEdenDest, { recursive: true });
|
|
69
|
-
packages.elysiaEden = true;
|
|
70
|
-
} catch (error) {
|
|
71
|
-
logger.error(`Failed to copy @elysiajs/eden: ${error.message}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return packages;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function processSingleRoute(route, config, defaultMeta, bundlePath, outDir, bertuiPackages) {
|
|
79
|
-
try {
|
|
80
|
-
const sourceCode = await Bun.file(route.path).text();
|
|
81
|
-
const pageMeta = extractMetaFromSource(sourceCode);
|
|
82
|
-
const meta = { ...defaultMeta, ...pageMeta };
|
|
83
|
-
|
|
84
|
-
const html = generateHTML(meta, bundlePath, bertuiPackages);
|
|
85
|
-
|
|
86
|
-
let htmlPath;
|
|
87
|
-
if (route.route === '/') {
|
|
88
|
-
htmlPath = join(outDir, 'index.html');
|
|
89
|
-
} else {
|
|
90
|
-
const routeDir = join(outDir, route.route.replace(/^\//, ''));
|
|
91
|
-
mkdirSync(routeDir, { recursive: true });
|
|
92
|
-
htmlPath = join(routeDir, 'index.html');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
await Bun.write(htmlPath, html);
|
|
96
|
-
logger.success(`${route.route}`);
|
|
97
|
-
|
|
98
|
-
} catch (error) {
|
|
99
|
-
logger.error(`Failed HTML for ${route.route}: ${error.message}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function generateHTML(meta, bundlePath, bertuiPackages = {}) {
|
|
104
|
-
const bertuiIconsImport = bertuiPackages.bertuiIcons
|
|
105
|
-
? ',\n "bertui-icons": "/node_modules/bertui-icons/generated/index.js"'
|
|
106
|
-
: '';
|
|
107
|
-
|
|
108
|
-
const bertuiAnimateCSS = bertuiPackages.bertuiAnimate
|
|
109
|
-
? ' <link rel="stylesheet" href="/css/bertui-animate.min.css">'
|
|
110
|
-
: '';
|
|
111
|
-
|
|
112
|
-
const elysiaEdenImport = bertuiPackages.elysiaEden
|
|
113
|
-
? ',\n "@elysiajs/eden": "/node_modules/@elysiajs/eden/dist/index.mjs"'
|
|
114
|
-
: '';
|
|
115
|
-
|
|
116
|
-
return `<!DOCTYPE html>
|
|
117
|
-
<html lang="${meta.lang || 'en'}">
|
|
118
|
-
<head>
|
|
119
|
-
<meta charset="UTF-8">
|
|
120
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
121
|
-
<title>${meta.title || 'BertUI App'}</title>
|
|
122
|
-
<meta name="description" content="${meta.description || 'Built with BertUI'}">
|
|
123
|
-
${meta.keywords ? `<meta name="keywords" content="${meta.keywords}">` : ''}
|
|
124
|
-
${meta.author ? `<meta name="author" content="${meta.author}">` : ''}
|
|
125
|
-
${meta.themeColor ? `<meta name="theme-color" content="${meta.themeColor}">` : ''}
|
|
126
|
-
<meta property="og:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
|
|
127
|
-
<meta property="og:description" content="${meta.ogDescription || meta.description || 'Built with BertUI'}">
|
|
128
|
-
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
129
|
-
<link rel="stylesheet" href="/styles/bertui.min.css">
|
|
130
|
-
${bertuiAnimateCSS}
|
|
131
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
132
|
-
<script type="importmap">
|
|
133
|
-
{
|
|
134
|
-
"imports": {
|
|
135
|
-
"react": "https://esm.sh/react@18.2.0",
|
|
136
|
-
"react-dom": "https://esm.sh/react-dom@18.2.0",
|
|
137
|
-
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
|
|
138
|
-
"react/jsx-runtime": "https://esm.sh/react@18.2.0/jsx-runtime",
|
|
139
|
-
"react/jsx-dev-runtime": "https://esm.sh/react@18.2.0/jsx-dev-runtime",
|
|
140
|
-
"@bunnyx/api": "/bunnyx-api/api-client.js",
|
|
141
|
-
"@elysiajs/eden": "/node_modules/@elysiajs/eden/dist/index.mjs"${bertuiIconsImport}${elysiaEdenImport}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
</script>
|
|
145
|
-
</head>
|
|
146
|
-
<body>
|
|
147
|
-
<div id="root"></div>
|
|
148
|
-
<script type="module" src="/${bundlePath}"></script>
|
|
149
|
-
</body>
|
|
150
|
-
</html>`;
|
|
151
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// bertui/src/build/robots-generator.js
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import logger from '../../logger/logger.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Generate robots.txt from sitemap data
|
|
7
|
-
* @param {Object} config - BertUI config with baseUrl
|
|
8
|
-
* @param {string} outDir - Output directory (dist/)
|
|
9
|
-
* @param {Array} routes - Optional: routes to disallow (e.g., admin pages)
|
|
10
|
-
*/
|
|
11
|
-
export async function generateRobots(config, outDir, routes = []) {
|
|
12
|
-
logger.info('🤖 Generating robots.txt...');
|
|
13
|
-
|
|
14
|
-
// ✅ FIX: Check if baseUrl exists, then remove trailing slash
|
|
15
|
-
if (!config?.baseUrl) {
|
|
16
|
-
logger.error('❌ baseUrl is required in bertui.config.js for robots.txt generation!');
|
|
17
|
-
logger.error(' Add: baseUrl: "https://your-domain.com" to your config');
|
|
18
|
-
throw new Error('Missing baseUrl in config - robots.txt generation failed');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const baseUrl = config.baseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
22
|
-
const sitemapUrl = `${baseUrl}/sitemap.xml`;
|
|
23
|
-
|
|
24
|
-
logger.info(` Sitemap URL: ${sitemapUrl}`);
|
|
25
|
-
|
|
26
|
-
// Default robots.txt - Allow all
|
|
27
|
-
let robotsTxt = `# BertUI Generated robots.txt
|
|
28
|
-
User-agent: *
|
|
29
|
-
Allow: /
|
|
30
|
-
|
|
31
|
-
# Sitemap
|
|
32
|
-
Sitemap: ${sitemapUrl}
|
|
33
|
-
`;
|
|
34
|
-
|
|
35
|
-
// Add custom disallow rules if specified in config
|
|
36
|
-
if (config?.robots?.disallow && Array.isArray(config.robots.disallow) && config.robots.disallow.length > 0) {
|
|
37
|
-
robotsTxt += '\n# Custom Disallow Rules\n';
|
|
38
|
-
config.robots.disallow.forEach(path => {
|
|
39
|
-
robotsTxt += `Disallow: ${path}\n`;
|
|
40
|
-
});
|
|
41
|
-
logger.info(` Blocked ${config.robots.disallow.length} path(s)`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Add crawl delay if specified
|
|
45
|
-
if (config?.robots?.crawlDelay && typeof config.robots.crawlDelay === 'number') {
|
|
46
|
-
robotsTxt += `\nCrawl-delay: ${config.robots.crawlDelay}\n`;
|
|
47
|
-
logger.info(` Crawl delay: ${config.robots.crawlDelay}s`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Write to dist/robots.txt
|
|
51
|
-
const robotsPath = join(outDir, 'robots.txt');
|
|
52
|
-
await Bun.write(robotsPath, robotsTxt);
|
|
53
|
-
|
|
54
|
-
logger.success('✅ robots.txt generated');
|
|
55
|
-
logger.info(` Location: ${robotsPath}`);
|
|
56
|
-
|
|
57
|
-
return { path: robotsPath, content: robotsTxt };
|
|
58
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// bertui/src/build/sitemap-generator.js - SIMPLIFIED
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import logger from '../../logger/logger.js';
|
|
4
|
-
|
|
5
|
-
function calculatePriority(route) {
|
|
6
|
-
if (route === '/') return 1.0;
|
|
7
|
-
if (route.includes(':')) return 0.6;
|
|
8
|
-
const depth = route.split('/').filter(Boolean).length;
|
|
9
|
-
if (depth === 1) return 0.8;
|
|
10
|
-
if (depth === 2) return 0.7;
|
|
11
|
-
return 0.6;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function generateSitemapXML(routes, baseUrl) {
|
|
15
|
-
const urls = routes.map(route => {
|
|
16
|
-
const url = `${baseUrl}${route.route}`;
|
|
17
|
-
return ` <url>
|
|
18
|
-
<loc>${url}</loc>
|
|
19
|
-
<lastmod>${route.lastmod}</lastmod>
|
|
20
|
-
<changefreq>weekly</changefreq>
|
|
21
|
-
<priority>${route.priority}</priority>
|
|
22
|
-
</url>`;
|
|
23
|
-
}).join('\n');
|
|
24
|
-
|
|
25
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
26
|
-
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
27
|
-
${urls}
|
|
28
|
-
</urlset>`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function generateSitemap(routes, config, outDir) {
|
|
32
|
-
logger.info('🗺️ Generating sitemap.xml...');
|
|
33
|
-
|
|
34
|
-
if (!config?.baseUrl) {
|
|
35
|
-
logger.error('❌ baseUrl is required in bertui.config.js for sitemap generation!');
|
|
36
|
-
logger.error(' Add: baseUrl: "https://your-domain.com" to your config');
|
|
37
|
-
throw new Error('Missing baseUrl in config - sitemap generation failed');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const baseUrl = config.baseUrl.replace(/\/$/, '');
|
|
41
|
-
const currentDate = new Date().toISOString().split('T')[0];
|
|
42
|
-
|
|
43
|
-
logger.info(` Base URL: ${baseUrl}`);
|
|
44
|
-
|
|
45
|
-
const staticRoutes = routes.filter(r => r.type === 'static');
|
|
46
|
-
logger.info(` ${staticRoutes.length} static routes will be included in sitemap`);
|
|
47
|
-
|
|
48
|
-
// SIMPLE: No file reading, just route processing
|
|
49
|
-
const sitemapRoutes = staticRoutes.map(route => ({
|
|
50
|
-
route: route.route,
|
|
51
|
-
lastmod: currentDate,
|
|
52
|
-
priority: calculatePriority(route.route)
|
|
53
|
-
}));
|
|
54
|
-
|
|
55
|
-
const xml = generateSitemapXML(sitemapRoutes, baseUrl);
|
|
56
|
-
const sitemapPath = join(outDir, 'sitemap.xml');
|
|
57
|
-
await Bun.write(sitemapPath, xml);
|
|
58
|
-
|
|
59
|
-
logger.success(`✅ Sitemap generated: ${sitemapRoutes.length} URLs`);
|
|
60
|
-
logger.info(` Location: ${sitemapPath}`);
|
|
61
|
-
|
|
62
|
-
return { routes: sitemapRoutes, path: sitemapPath };
|
|
63
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
// bertui/src/build/image-optimizer.js - UPDATED WITH WASM
|
|
2
|
-
import { join, extname } from 'path';
|
|
3
|
-
import { existsSync, mkdirSync, readdirSync } from 'fs';
|
|
4
|
-
import logger from '../logger/logger.js';
|
|
5
|
-
import { optimizeImage, hasWasm } from '../image-optimizer/index.js';
|
|
6
|
-
import { copyImagesSync } from '../images/index.js';
|
|
7
|
-
|
|
8
|
-
export async function optimizeImages(srcDir, outDir, options = {}) {
|
|
9
|
-
const {
|
|
10
|
-
quality = 80,
|
|
11
|
-
webpQuality = 75,
|
|
12
|
-
verbose = false
|
|
13
|
-
} = options;
|
|
14
|
-
|
|
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
|
-
}
|
|
23
|
-
|
|
24
|
-
const imageExtensions = [
|
|
25
|
-
'.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg',
|
|
26
|
-
'.avif', '.ico', '.bmp', '.tiff', '.tif'
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
let optimized = 0;
|
|
30
|
-
let copied = 0;
|
|
31
|
-
let skipped = 0;
|
|
32
|
-
let totalSaved = 0;
|
|
33
|
-
const results = [];
|
|
34
|
-
|
|
35
|
-
if (!existsSync(srcDir)) {
|
|
36
|
-
logger.warn(`⚠️ Source not found: ${srcDir}`);
|
|
37
|
-
return { optimized: 0, copied: 0, saved: 0, results: [] };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
mkdirSync(outDir, { recursive: true });
|
|
41
|
-
|
|
42
|
-
async function processDirectory(dir, targetDir) {
|
|
43
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
44
|
-
|
|
45
|
-
for (const entry of entries) {
|
|
46
|
-
const srcPath = join(dir, entry.name);
|
|
47
|
-
const destPath = join(targetDir, entry.name);
|
|
48
|
-
|
|
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);
|
|
87
|
-
copied++;
|
|
88
|
-
results.push({
|
|
89
|
-
file: entry.name,
|
|
90
|
-
status: 'copied'
|
|
91
|
-
});
|
|
92
|
-
}
|
|
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++;
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
// Copy non-image files
|
|
101
|
-
await Bun.write(destPath, Bun.file(srcPath));
|
|
102
|
-
skipped++;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
await processDirectory(srcDir, outDir);
|
|
109
|
-
|
|
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)}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (copied > 0) {
|
|
121
|
-
logger.info(`📋 Copied ${copied} images (fallback)`);
|
|
122
|
-
}
|
|
123
|
-
|
|
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]}`;
|
|
137
|
-
}
|