bertui 1.2.9 β 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 +44 -242
- 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 -263
- 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 -67
- package/src/build/ssr-renderer.js +0 -64
- package/src/build.js +0 -273
- 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,263 +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
|
-
import { renderPageToHTML, getPageRenderMode } from '../ssr-renderer.js'
|
|
7
|
-
|
|
8
|
-
export async function generateProductionHTML(root, outDir, buildResult, routes, config, buildDir) {
|
|
9
|
-
const mainBundle = buildResult.outputs.find(o =>
|
|
10
|
-
o.path.includes('main') && o.kind === 'entry-point'
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
if (!mainBundle) {
|
|
14
|
-
logger.error('Could not find main bundle')
|
|
15
|
-
return
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const bundlePath = relative(outDir, mainBundle.path).replace(/\\/g, '/')
|
|
19
|
-
const defaultMeta = config.meta || {}
|
|
20
|
-
const bertuiPackages = await copyBertuiPackagesToProduction(root, outDir)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
logger.info(`Generating HTML for ${routes.length} routes...`)
|
|
24
|
-
|
|
25
|
-
for (const route of routes) {
|
|
26
|
-
await processSingleRoute(route, config, defaultMeta, bundlePath, outDir, bertuiPackages, buildDir)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
logger.success(`HTML generation complete for ${routes.length} routes`)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function processSingleRoute(route, config, defaultMeta, bundlePath, outDir, bertuiPackages, buildDir) {
|
|
33
|
-
try {
|
|
34
|
-
const sourceCode = await Bun.file(route.path).text()
|
|
35
|
-
const pageMeta = extractMetaFromSource(sourceCode)
|
|
36
|
-
const meta = { ...defaultMeta, ...pageMeta }
|
|
37
|
-
|
|
38
|
-
// Determine render mode from source
|
|
39
|
-
const renderMode = await getPageRenderMode(route.path)
|
|
40
|
-
|
|
41
|
-
let html
|
|
42
|
-
|
|
43
|
-
if (renderMode === 'server' || renderMode === 'static') {
|
|
44
|
-
// Find the compiled version of this page in buildDir
|
|
45
|
-
const compiledPath = findCompiledPath(route, buildDir)
|
|
46
|
-
|
|
47
|
-
if (compiledPath && existsSync(compiledPath)) {
|
|
48
|
-
logger.info(` SSR rendering: ${route.route}`)
|
|
49
|
-
const ssrHTML = await renderPageToHTML(compiledPath, buildDir)
|
|
50
|
-
|
|
51
|
-
if (ssrHTML) {
|
|
52
|
-
if (renderMode === 'static') {
|
|
53
|
-
// Pure static β no JS at all
|
|
54
|
-
html = generateStaticHTML({ ssrHTML, meta, bertuiPackages })
|
|
55
|
-
} else {
|
|
56
|
-
// Server island β SSR HTML + JS bundle for hydration
|
|
57
|
-
html = generateServerIslandHTML({ ssrHTML, meta, bundlePath, bertuiPackages })
|
|
58
|
-
}
|
|
59
|
-
logger.success(` β SSR: ${route.route} (${renderMode})`)
|
|
60
|
-
} else {
|
|
61
|
-
// SSR failed β fall back to client render with a warning
|
|
62
|
-
logger.warn(` SSR failed for ${route.route}, falling back to client render`)
|
|
63
|
-
html = generateClientHTML({ meta, bundlePath, bertuiPackages })
|
|
64
|
-
}
|
|
65
|
-
} else {
|
|
66
|
-
logger.warn(` Compiled path not found for ${route.route}, using client render`)
|
|
67
|
-
html = generateClientHTML({ meta, bundlePath, bertuiPackages })
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
// Default: client-only SPA
|
|
71
|
-
html = generateClientHTML({ meta, bundlePath, bertuiPackages })
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Write to dist/
|
|
75
|
-
let htmlPath
|
|
76
|
-
if (route.route === '/') {
|
|
77
|
-
htmlPath = join(outDir, 'index.html')
|
|
78
|
-
} else {
|
|
79
|
-
const routeDir = join(outDir, route.route.replace(/^\//, ''))
|
|
80
|
-
mkdirSync(routeDir, { recursive: true })
|
|
81
|
-
htmlPath = join(routeDir, 'index.html')
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
await Bun.write(htmlPath, html)
|
|
85
|
-
logger.success(` ${route.route}`)
|
|
86
|
-
|
|
87
|
-
} catch (error) {
|
|
88
|
-
logger.error(`Failed HTML for ${route.route}: ${error.message}`)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Find the compiled .js path for a route's source file
|
|
94
|
-
*/
|
|
95
|
-
function findCompiledPath(route, buildDir) {
|
|
96
|
-
const compiledFile = route.file.replace(/\.(jsx|tsx|ts)$/, '.js')
|
|
97
|
-
return join(buildDir, 'pages', compiledFile)
|
|
98
|
-
}
|
|
99
|
-
// βββ HTML generators ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* render = "static" β pure HTML, zero JS
|
|
103
|
-
*/
|
|
104
|
-
function generateStaticHTML({ ssrHTML, meta, bertuiPackages }) {
|
|
105
|
-
const bertuiAnimateCSS = bertuiPackages.bertuiAnimate
|
|
106
|
-
? '<link rel="stylesheet" href="/css/bertui-animate.min.css">'
|
|
107
|
-
: ''
|
|
108
|
-
|
|
109
|
-
return `<!DOCTYPE html>
|
|
110
|
-
<html lang="${meta.lang || 'en'}">
|
|
111
|
-
<head>
|
|
112
|
-
<meta charset="UTF-8">
|
|
113
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
114
|
-
<title>${meta.title || 'BertUI App'}</title>
|
|
115
|
-
<meta name="description" content="${meta.description || ''}">
|
|
116
|
-
${meta.keywords ? `<meta name="keywords" content="${meta.keywords}">` : ''}
|
|
117
|
-
${meta.author ? `<meta name="author" content="${meta.author}">` : ''}
|
|
118
|
-
${meta.themeColor ? `<meta name="theme-color" content="${meta.themeColor}">` : ''}
|
|
119
|
-
<meta property="og:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
|
|
120
|
-
<meta property="og:description" content="${meta.ogDescription || meta.description || ''}">
|
|
121
|
-
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
122
|
-
<link rel="stylesheet" href="/styles/bertui.min.css">
|
|
123
|
-
${bertuiAnimateCSS}
|
|
124
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
125
|
-
</head>
|
|
126
|
-
<body>
|
|
127
|
-
${ssrHTML}
|
|
128
|
-
</body>
|
|
129
|
-
</html>`
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* render = "server" β SSR HTML + JS bundle for hydration
|
|
134
|
-
*/
|
|
135
|
-
function generateServerIslandHTML({ ssrHTML, meta, bundlePath, bertuiPackages }) {
|
|
136
|
-
const { importMapScript, bertuiAnimateCSS } = buildSharedAssets(meta, bertuiPackages)
|
|
137
|
-
|
|
138
|
-
return `<!DOCTYPE html>
|
|
139
|
-
<html lang="${meta.lang || 'en'}">
|
|
140
|
-
<head>
|
|
141
|
-
<meta charset="UTF-8">
|
|
142
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
143
|
-
<title>${meta.title || 'BertUI App'}</title>
|
|
144
|
-
<meta name="description" content="${meta.description || ''}">
|
|
145
|
-
${meta.keywords ? `<meta name="keywords" content="${meta.keywords}">` : ''}
|
|
146
|
-
${meta.author ? `<meta name="author" content="${meta.author}">` : ''}
|
|
147
|
-
${meta.themeColor ? `<meta name="theme-color" content="${meta.themeColor}">` : ''}
|
|
148
|
-
<meta property="og:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
|
|
149
|
-
<meta property="og:description" content="${meta.ogDescription || meta.description || ''}">
|
|
150
|
-
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
151
|
-
<link rel="stylesheet" href="/styles/bertui.min.css">
|
|
152
|
-
${bertuiAnimateCSS}
|
|
153
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
154
|
-
${importMapScript}
|
|
155
|
-
</head>
|
|
156
|
-
<body>
|
|
157
|
-
<div id="root">${ssrHTML}</div>
|
|
158
|
-
<script type="module" src="/${bundlePath}"></script>
|
|
159
|
-
</body>
|
|
160
|
-
</html>`
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* default β client-only SPA (existing behavior)
|
|
165
|
-
*/
|
|
166
|
-
function generateClientHTML({ meta, bundlePath, bertuiPackages }) {
|
|
167
|
-
const { importMapScript, bertuiAnimateCSS } = buildSharedAssets(meta, bertuiPackages)
|
|
168
|
-
|
|
169
|
-
return `<!DOCTYPE html>
|
|
170
|
-
<html lang="${meta.lang || 'en'}">
|
|
171
|
-
<head>
|
|
172
|
-
<meta charset="UTF-8">
|
|
173
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
174
|
-
<title>${meta.title || 'BertUI App'}</title>
|
|
175
|
-
<meta name="description" content="${meta.description || ''}">
|
|
176
|
-
${meta.keywords ? `<meta name="keywords" content="${meta.keywords}">` : ''}
|
|
177
|
-
${meta.author ? `<meta name="author" content="${meta.author}">` : ''}
|
|
178
|
-
${meta.themeColor ? `<meta name="theme-color" content="${meta.themeColor}">` : ''}
|
|
179
|
-
<meta property="og:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
|
|
180
|
-
<meta property="og:description" content="${meta.ogDescription || meta.description || ''}">
|
|
181
|
-
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
182
|
-
<link rel="stylesheet" href="/styles/bertui.min.css">
|
|
183
|
-
${bertuiAnimateCSS}
|
|
184
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
185
|
-
${importMapScript}
|
|
186
|
-
</head>
|
|
187
|
-
<body>
|
|
188
|
-
<div id="root"></div>
|
|
189
|
-
<script type="module" src="/${bundlePath}"></script>
|
|
190
|
-
</body>
|
|
191
|
-
</html>`
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// βββ Shared helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
195
|
-
|
|
196
|
-
function buildSharedAssets(meta, bertuiPackages) {
|
|
197
|
-
const bertuiIconsImport = bertuiPackages.bertuiIcons
|
|
198
|
-
? ',\n "bertui-icons": "/node_modules/bertui-icons/generated/index.js"'
|
|
199
|
-
: ''
|
|
200
|
-
const elysiaEdenImport = bertuiPackages.elysiaEden
|
|
201
|
-
? ',\n "@elysiajs/eden": "/node_modules/@elysiajs/eden/dist/index.mjs"'
|
|
202
|
-
: ''
|
|
203
|
-
const bertuiAnimateCSS = bertuiPackages.bertuiAnimate
|
|
204
|
-
? '<link rel="stylesheet" href="/css/bertui-animate.min.css">'
|
|
205
|
-
: ''
|
|
206
|
-
|
|
207
|
-
const importMapScript = `<script type="importmap">
|
|
208
|
-
{
|
|
209
|
-
"imports": {
|
|
210
|
-
"react": "https://esm.sh/react@18.2.0",
|
|
211
|
-
"react-dom": "https://esm.sh/react-dom@18.2.0",
|
|
212
|
-
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
|
|
213
|
-
"react/jsx-runtime": "https://esm.sh/react@18.2.0/jsx-runtime",
|
|
214
|
-
"react/jsx-dev-runtime": "https://esm.sh/react@18.2.0/jsx-dev-runtime",
|
|
215
|
-
"@bunnyx/api": "/bunnyx-api/api-client.js"${bertuiIconsImport}${elysiaEdenImport}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
</script>`
|
|
219
|
-
|
|
220
|
-
return { importMapScript, bertuiAnimateCSS }
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async function copyBertuiPackagesToProduction(root, outDir) {
|
|
224
|
-
const nodeModulesDir = join(root, 'node_modules')
|
|
225
|
-
const packages = { bertuiIcons: false, bertuiAnimate: false, elysiaEden: false }
|
|
226
|
-
|
|
227
|
-
if (!existsSync(nodeModulesDir)) return packages
|
|
228
|
-
|
|
229
|
-
const bertuiIconsSrc = join(nodeModulesDir, 'bertui-icons')
|
|
230
|
-
if (existsSync(bertuiIconsSrc)) {
|
|
231
|
-
try {
|
|
232
|
-
const dest = join(outDir, 'node_modules', 'bertui-icons')
|
|
233
|
-
mkdirSync(join(outDir, 'node_modules'), { recursive: true })
|
|
234
|
-
cpSync(bertuiIconsSrc, dest, { recursive: true })
|
|
235
|
-
packages.bertuiIcons = true
|
|
236
|
-
} catch {}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const bertuiAnimateSrc = join(nodeModulesDir, 'bertui-animate', 'dist')
|
|
240
|
-
if (existsSync(bertuiAnimateSrc)) {
|
|
241
|
-
try {
|
|
242
|
-
const dest = join(outDir, 'css')
|
|
243
|
-
mkdirSync(dest, { recursive: true })
|
|
244
|
-
const minCSS = join(bertuiAnimateSrc, 'bertui-animate.min.css')
|
|
245
|
-
if (existsSync(minCSS)) {
|
|
246
|
-
cpSync(minCSS, join(dest, 'bertui-animate.min.css'))
|
|
247
|
-
packages.bertuiAnimate = true
|
|
248
|
-
}
|
|
249
|
-
} catch {}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const elysiaEdenSrc = join(nodeModulesDir, '@elysiajs', 'eden')
|
|
253
|
-
if (existsSync(elysiaEdenSrc)) {
|
|
254
|
-
try {
|
|
255
|
-
const dest = join(outDir, 'node_modules', '@elysiajs', 'eden')
|
|
256
|
-
mkdirSync(join(outDir, 'node_modules', '@elysiajs'), { recursive: true })
|
|
257
|
-
cpSync(elysiaEdenSrc, dest, { recursive: true })
|
|
258
|
-
packages.elysiaEden = true
|
|
259
|
-
} catch {}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return packages
|
|
263
|
-
}
|
|
@@ -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
|
-
}
|