bertui 1.0.3 → 1.1.1
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 +551 -132
- package/package.json +27 -7
- package/src/build/compiler/file-transpiler.js +171 -0
- package/src/build/compiler/index.js +45 -0
- package/src/build/compiler/route-discoverer.js +46 -0
- package/src/build/compiler/router-generator.js +104 -0
- package/src/build/generators/html-generator.js +259 -0
- package/src/build/generators/robots-generator.js +58 -0
- package/src/build/generators/sitemap-generator.js +63 -0
- package/src/build/processors/asset-processor.js +19 -0
- package/src/build/processors/css-builder.js +35 -0
- package/src/build/server-island-validator.js +156 -0
- package/src/build.js +96 -632
- package/src/config/defaultConfig.js +26 -6
- package/src/config/loadConfig.js +21 -5
- package/src/router/Router.js +38 -5
- package/src/router/SSRRouter.js +156 -0
- package/src/utils/meta-extractor.js +61 -0
- package/types/config.d.ts +80 -0
- package/types/index.d.ts +116 -0
- package/types/react.d.ts +13 -0
- package/types/router.d.ts +79 -0
|
@@ -1,16 +1,36 @@
|
|
|
1
|
+
// bertui/src/config/defaultConfig.js
|
|
2
|
+
// Default configuration used when bertui.config.js is not present
|
|
3
|
+
|
|
1
4
|
export const defaultConfig = {
|
|
5
|
+
// Site information (used for sitemap generation)
|
|
6
|
+
siteName: "BertUI App",
|
|
7
|
+
baseUrl: "http://localhost:3000", // Default to localhost
|
|
8
|
+
|
|
9
|
+
// HTML Meta Tags (SEO)
|
|
2
10
|
meta: {
|
|
3
|
-
title: "BertUI
|
|
4
|
-
description: "
|
|
5
|
-
keywords: "react, bun, bertui",
|
|
11
|
+
title: "BertUI - Lightning Fast React",
|
|
12
|
+
description: "Build lightning-fast React applications with file-based routing powered by Bun",
|
|
13
|
+
keywords: "react, bun, bertui, fast, file-based routing",
|
|
6
14
|
author: "Pease Ernest",
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
themeColor: "#667eea",
|
|
16
|
+
lang: "en",
|
|
17
|
+
|
|
18
|
+
// Open Graph for social sharing
|
|
19
|
+
ogTitle: "BertUI - Lightning Fast React Framework",
|
|
20
|
+
ogDescription: "Build lightning-fast React apps with zero config",
|
|
21
|
+
ogImage: "/og-image.png"
|
|
10
22
|
},
|
|
23
|
+
|
|
24
|
+
// App Shell Configuration
|
|
11
25
|
appShell: {
|
|
12
26
|
loading: true,
|
|
13
27
|
loadingText: "Loading...",
|
|
14
28
|
backgroundColor: "#ffffff"
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// robots.txt Configuration
|
|
32
|
+
robots: {
|
|
33
|
+
disallow: [], // No paths blocked by default
|
|
34
|
+
crawlDelay: null // No crawl delay by default
|
|
15
35
|
}
|
|
16
36
|
};
|
package/src/config/loadConfig.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/config/loadConfig.js
|
|
1
|
+
// src/config/loadConfig.js - COMPLETE CORRECTED VERSION
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { defaultConfig } from './defaultConfig.js';
|
|
@@ -13,6 +13,13 @@ export async function loadConfig(root) {
|
|
|
13
13
|
const userConfig = await import(configPath);
|
|
14
14
|
logger.success('Loaded bertui.config.js');
|
|
15
15
|
|
|
16
|
+
// DEBUG: Show what we loaded
|
|
17
|
+
logger.info(`📋 Config loaded: ${JSON.stringify({
|
|
18
|
+
hasSiteName: !!(userConfig.default?.siteName || userConfig.siteName),
|
|
19
|
+
hasBaseUrl: !!(userConfig.default?.baseUrl || userConfig.baseUrl),
|
|
20
|
+
hasRobots: !!(userConfig.default?.robots || userConfig.robots)
|
|
21
|
+
})}`);
|
|
22
|
+
|
|
16
23
|
// Merge user config with defaults
|
|
17
24
|
return mergeConfig(defaultConfig, userConfig.default || userConfig);
|
|
18
25
|
} catch (error) {
|
|
@@ -26,8 +33,17 @@ export async function loadConfig(root) {
|
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
function mergeConfig(defaults, user) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
// Start with user config (so user values override defaults)
|
|
37
|
+
const merged = { ...user };
|
|
38
|
+
|
|
39
|
+
// Deep merge for nested objects
|
|
40
|
+
merged.meta = { ...defaults.meta, ...(user.meta || {}) };
|
|
41
|
+
merged.appShell = { ...defaults.appShell, ...(user.appShell || {}) };
|
|
42
|
+
merged.robots = { ...defaults.robots, ...(user.robots || {}) };
|
|
43
|
+
|
|
44
|
+
// Ensure we have required top-level fields
|
|
45
|
+
if (!merged.siteName) merged.siteName = defaults.siteName;
|
|
46
|
+
if (!merged.baseUrl) merged.baseUrl = defaults.baseUrl;
|
|
47
|
+
|
|
48
|
+
return merged;
|
|
33
49
|
}
|
package/src/router/Router.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
|
+
// src/router/Router.js - SSR COMPATIBLE VERSION
|
|
1
2
|
import { useState, useEffect, createContext, useContext } from 'react';
|
|
2
3
|
|
|
3
4
|
const RouterContext = createContext(null);
|
|
4
5
|
|
|
6
|
+
// ✅ FIX: SSR-safe useRouter
|
|
5
7
|
export function useRouter() {
|
|
6
8
|
const context = useContext(RouterContext);
|
|
9
|
+
|
|
10
|
+
// During SSR (when window doesn't exist), return a mock router
|
|
11
|
+
if (typeof window === 'undefined') {
|
|
12
|
+
return {
|
|
13
|
+
pathname: '/',
|
|
14
|
+
params: {},
|
|
15
|
+
navigate: () => {},
|
|
16
|
+
currentRoute: null,
|
|
17
|
+
isSSR: true
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
7
21
|
if (!context) {
|
|
8
22
|
throw new Error('useRouter must be used within a Router component');
|
|
9
23
|
}
|
|
24
|
+
|
|
10
25
|
return context;
|
|
11
26
|
}
|
|
12
27
|
|
|
@@ -62,15 +77,18 @@ export function Router({ routes }) {
|
|
|
62
77
|
}
|
|
63
78
|
|
|
64
79
|
function navigate(path) {
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
if (typeof window !== 'undefined') {
|
|
81
|
+
window.history.pushState({}, '', path);
|
|
82
|
+
matchAndSetRoute(path);
|
|
83
|
+
}
|
|
67
84
|
}
|
|
68
85
|
|
|
69
86
|
const routerValue = {
|
|
70
87
|
currentRoute,
|
|
71
88
|
params,
|
|
72
89
|
navigate,
|
|
73
|
-
pathname: window.location.pathname
|
|
90
|
+
pathname: typeof window !== 'undefined' ? window.location.pathname : '/',
|
|
91
|
+
isSSR: typeof window === 'undefined'
|
|
74
92
|
};
|
|
75
93
|
|
|
76
94
|
const Component = currentRoute?.component;
|
|
@@ -82,12 +100,27 @@ export function Router({ routes }) {
|
|
|
82
100
|
);
|
|
83
101
|
}
|
|
84
102
|
|
|
103
|
+
// ✅ FIX: SSR-safe Link component
|
|
85
104
|
export function Link({ to, children, ...props }) {
|
|
86
|
-
|
|
105
|
+
// Try to get router, but don't fail if it doesn't exist
|
|
106
|
+
let router;
|
|
107
|
+
try {
|
|
108
|
+
router = useRouter();
|
|
109
|
+
} catch (e) {
|
|
110
|
+
// During SSR, router might not be available
|
|
111
|
+
router = null;
|
|
112
|
+
}
|
|
87
113
|
|
|
88
114
|
function handleClick(e) {
|
|
115
|
+
// During SSR, just use normal link behavior
|
|
116
|
+
if (typeof window === 'undefined') return;
|
|
117
|
+
|
|
118
|
+
// If no router or navigate function, use normal link
|
|
119
|
+
if (!router || !router.navigate) return;
|
|
120
|
+
|
|
121
|
+
// Only prevent default if we have client-side routing
|
|
89
122
|
e.preventDefault();
|
|
90
|
-
navigate(to);
|
|
123
|
+
router.navigate(to);
|
|
91
124
|
}
|
|
92
125
|
|
|
93
126
|
return (
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// src/router/SSRRouter.jsx
|
|
2
|
+
// SSR-Compatible Router for BertUI - Works during build AND runtime
|
|
3
|
+
import React, { useState, useEffect, createContext, useContext } from 'react';
|
|
4
|
+
|
|
5
|
+
const RouterContext = createContext(null);
|
|
6
|
+
|
|
7
|
+
// ✅ SSR-safe useRouter hook
|
|
8
|
+
export function useRouter() {
|
|
9
|
+
const context = useContext(RouterContext);
|
|
10
|
+
|
|
11
|
+
// During SSR, provide a mock router
|
|
12
|
+
if (!context) {
|
|
13
|
+
return {
|
|
14
|
+
pathname: '/',
|
|
15
|
+
params: {},
|
|
16
|
+
navigate: () => {},
|
|
17
|
+
isSSR: true
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return context;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ✅ SSR-safe Router component
|
|
25
|
+
export function Router({ routes, initialPath = '/' }) {
|
|
26
|
+
const [currentRoute, setCurrentRoute] = useState(null);
|
|
27
|
+
const [params, setParams] = useState({});
|
|
28
|
+
const [isClient, setIsClient] = useState(false);
|
|
29
|
+
|
|
30
|
+
// Detect if we're in the browser
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
setIsClient(true);
|
|
33
|
+
matchAndSetRoute(window.location.pathname);
|
|
34
|
+
|
|
35
|
+
const handlePopState = () => {
|
|
36
|
+
matchAndSetRoute(window.location.pathname);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
window.addEventListener('popstate', handlePopState);
|
|
40
|
+
return () => window.removeEventListener('popstate', handlePopState);
|
|
41
|
+
}, [routes]);
|
|
42
|
+
|
|
43
|
+
// Match route on server-side (SSR)
|
|
44
|
+
if (!isClient && !currentRoute) {
|
|
45
|
+
const matched = matchRoute(initialPath, routes);
|
|
46
|
+
if (matched) {
|
|
47
|
+
return React.createElement(
|
|
48
|
+
RouterContext.Provider,
|
|
49
|
+
{ value: { pathname: initialPath, params: matched.params, navigate: () => {}, isSSR: true } },
|
|
50
|
+
React.createElement(matched.route.component, { params: matched.params })
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function matchRoute(pathname, routesList) {
|
|
56
|
+
// Try static routes first
|
|
57
|
+
for (const route of routesList) {
|
|
58
|
+
if (route.type === 'static' && route.path === pathname) {
|
|
59
|
+
return { route, params: {} };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Try dynamic routes
|
|
64
|
+
for (const route of routesList) {
|
|
65
|
+
if (route.type === 'dynamic') {
|
|
66
|
+
const pattern = route.path.replace(/\[([^\]]+)\]/g, '([^/]+)');
|
|
67
|
+
const regex = new RegExp('^' + pattern + '$');
|
|
68
|
+
const match = pathname.match(regex);
|
|
69
|
+
|
|
70
|
+
if (match) {
|
|
71
|
+
const paramNames = [...route.path.matchAll(/\[([^\]]+)\]/g)].map(m => m[1]);
|
|
72
|
+
const extractedParams = {};
|
|
73
|
+
paramNames.forEach((name, i) => {
|
|
74
|
+
extractedParams[name] = match[i + 1];
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return { route, params: extractedParams };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function matchAndSetRoute(pathname) {
|
|
86
|
+
const matched = matchRoute(pathname, routes);
|
|
87
|
+
|
|
88
|
+
if (matched) {
|
|
89
|
+
setCurrentRoute(matched.route);
|
|
90
|
+
setParams(matched.params);
|
|
91
|
+
} else {
|
|
92
|
+
setCurrentRoute(null);
|
|
93
|
+
setParams({});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function navigate(path) {
|
|
98
|
+
if (typeof window !== 'undefined') {
|
|
99
|
+
window.history.pushState({}, '', path);
|
|
100
|
+
matchAndSetRoute(path);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const routerValue = {
|
|
105
|
+
currentRoute,
|
|
106
|
+
params,
|
|
107
|
+
navigate,
|
|
108
|
+
pathname: typeof window !== 'undefined' ? window.location.pathname : initialPath,
|
|
109
|
+
isSSR: !isClient
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const Component = currentRoute?.component;
|
|
113
|
+
|
|
114
|
+
return React.createElement(
|
|
115
|
+
RouterContext.Provider,
|
|
116
|
+
{ value: routerValue },
|
|
117
|
+
Component ? React.createElement(Component, { params }) : React.createElement(NotFound, null)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ✅ SSR-safe Link component
|
|
122
|
+
export function Link({ to, children, ...props }) {
|
|
123
|
+
const { navigate, isSSR } = useRouter();
|
|
124
|
+
|
|
125
|
+
function handleClick(e) {
|
|
126
|
+
// Don't prevent default during SSR
|
|
127
|
+
if (isSSR) return;
|
|
128
|
+
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
navigate(to);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return React.createElement('a', { href: to, onClick: handleClick, ...props }, children);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function NotFound() {
|
|
137
|
+
return React.createElement(
|
|
138
|
+
'div',
|
|
139
|
+
{
|
|
140
|
+
style: {
|
|
141
|
+
display: 'flex',
|
|
142
|
+
flexDirection: 'column',
|
|
143
|
+
alignItems: 'center',
|
|
144
|
+
justifyContent: 'center',
|
|
145
|
+
minHeight: '100vh',
|
|
146
|
+
fontFamily: 'system-ui'
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
React.createElement('h1', { style: { fontSize: '6rem', margin: 0 } }, '404'),
|
|
150
|
+
React.createElement('p', { style: { fontSize: '1.5rem', color: '#666' } }, 'Page not found'),
|
|
151
|
+
React.createElement('a', {
|
|
152
|
+
href: '/',
|
|
153
|
+
style: { color: '#10b981', textDecoration: 'none', fontSize: '1.2rem' }
|
|
154
|
+
}, 'Go home')
|
|
155
|
+
);
|
|
156
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// bertui/src/utils/meta-extractor.js
|
|
2
|
+
export function extractMetaFromSource(code) {
|
|
3
|
+
try {
|
|
4
|
+
if (!code.includes('export const meta')) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const metaStart = code.indexOf('export const meta = {');
|
|
9
|
+
if (metaStart === -1) return null;
|
|
10
|
+
|
|
11
|
+
let braceCount = 0;
|
|
12
|
+
let inString = false;
|
|
13
|
+
let stringChar = '';
|
|
14
|
+
let metaEnd = -1;
|
|
15
|
+
|
|
16
|
+
for (let i = metaStart + 'export const meta = {'.length; i < code.length; i++) {
|
|
17
|
+
const char = code[i];
|
|
18
|
+
const prevChar = i > 0 ? code[i - 1] : '';
|
|
19
|
+
|
|
20
|
+
if (!inString && (char === '"' || char === "'" || char === '`')) {
|
|
21
|
+
inString = true;
|
|
22
|
+
stringChar = char;
|
|
23
|
+
} else if (inString && char === stringChar && prevChar !== '\\') {
|
|
24
|
+
inString = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!inString) {
|
|
28
|
+
if (char === '{') braceCount++;
|
|
29
|
+
if (char === '}') {
|
|
30
|
+
if (braceCount === 0) {
|
|
31
|
+
metaEnd = i;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
braceCount--;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (metaEnd === -1) return null;
|
|
40
|
+
|
|
41
|
+
const metaString = code.substring(metaStart + 'export const meta = {'.length - 1, metaEnd + 1);
|
|
42
|
+
const meta = {};
|
|
43
|
+
const pairs = metaString.match(/(\w+)\s*:\s*(['"`][^'"`]*['"`])/g) || [];
|
|
44
|
+
|
|
45
|
+
pairs.forEach(pair => {
|
|
46
|
+
const colonIndex = pair.indexOf(':');
|
|
47
|
+
if (colonIndex === -1) return;
|
|
48
|
+
|
|
49
|
+
const key = pair.substring(0, colonIndex).trim();
|
|
50
|
+
const value = pair.substring(colonIndex + 1).trim().slice(1, -1);
|
|
51
|
+
|
|
52
|
+
if (key && value) {
|
|
53
|
+
meta[key] = value;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return Object.keys(meta).length > 0 ? meta : null;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// bertui/types/config.d.ts
|
|
2
|
+
declare module 'bertui/config' {
|
|
3
|
+
/**
|
|
4
|
+
* BertUI Configuration
|
|
5
|
+
*/
|
|
6
|
+
export interface BertuiConfig {
|
|
7
|
+
/** Site name for SEO */
|
|
8
|
+
siteName?: string;
|
|
9
|
+
|
|
10
|
+
/** Base URL for sitemap generation (e.g., "https://example.com") */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
|
|
13
|
+
/** HTML meta tags configuration */
|
|
14
|
+
meta?: {
|
|
15
|
+
/** Page title */
|
|
16
|
+
title?: string;
|
|
17
|
+
/** Meta description */
|
|
18
|
+
description?: string;
|
|
19
|
+
/** Meta keywords */
|
|
20
|
+
keywords?: string;
|
|
21
|
+
/** Author name */
|
|
22
|
+
author?: string;
|
|
23
|
+
/** Open Graph image URL */
|
|
24
|
+
ogImage?: string;
|
|
25
|
+
/** Open Graph title */
|
|
26
|
+
ogTitle?: string;
|
|
27
|
+
/** Open Graph description */
|
|
28
|
+
ogDescription?: string;
|
|
29
|
+
/** Theme color */
|
|
30
|
+
themeColor?: string;
|
|
31
|
+
/** Language code (e.g., "en") */
|
|
32
|
+
lang?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** App shell configuration */
|
|
36
|
+
appShell?: {
|
|
37
|
+
/** Show loading indicator */
|
|
38
|
+
loading?: boolean;
|
|
39
|
+
/** Loading text */
|
|
40
|
+
loadingText?: string;
|
|
41
|
+
/** Background color */
|
|
42
|
+
backgroundColor?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** robots.txt configuration */
|
|
46
|
+
robots?: {
|
|
47
|
+
/** Paths to disallow in robots.txt */
|
|
48
|
+
disallow?: string[];
|
|
49
|
+
/** Crawl delay in seconds */
|
|
50
|
+
crawlDelay?: number;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Page meta configuration (exported from page files)
|
|
56
|
+
*/
|
|
57
|
+
export interface PageMeta {
|
|
58
|
+
title?: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
keywords?: string;
|
|
61
|
+
author?: string;
|
|
62
|
+
ogTitle?: string;
|
|
63
|
+
ogDescription?: string;
|
|
64
|
+
ogImage?: string;
|
|
65
|
+
themeColor?: string;
|
|
66
|
+
lang?: string;
|
|
67
|
+
publishedDate?: string;
|
|
68
|
+
updatedDate?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Default BertUI configuration
|
|
73
|
+
*/
|
|
74
|
+
export const defaultConfig: BertuiConfig;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Load BertUI configuration from bertui.config.js
|
|
78
|
+
*/
|
|
79
|
+
export function loadConfig(root: string): Promise<BertuiConfig>;
|
|
80
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// bertui/types/index.d.ts
|
|
2
|
+
|
|
3
|
+
declare namespace React {
|
|
4
|
+
type ReactNode = any;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
namespace JSX {
|
|
9
|
+
type Element = any;
|
|
10
|
+
interface IntrinsicElements {
|
|
11
|
+
[elemName: string]: any;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
declare module 'bertui' {
|
|
16
|
+
import { BertuiConfig } from 'bertui/config';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Logger utility
|
|
20
|
+
*/
|
|
21
|
+
export interface Logger {
|
|
22
|
+
info(message: string): void;
|
|
23
|
+
success(message: string): void;
|
|
24
|
+
warn(message: string): void;
|
|
25
|
+
error(message: string): void;
|
|
26
|
+
debug(message: string): void;
|
|
27
|
+
bigLog(message: string, options?: { color?: string }): void;
|
|
28
|
+
table(data: any[]): void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build options
|
|
33
|
+
*/
|
|
34
|
+
export interface BuildOptions {
|
|
35
|
+
/** Project root directory */
|
|
36
|
+
root?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Dev server options
|
|
41
|
+
*/
|
|
42
|
+
export interface DevOptions {
|
|
43
|
+
/** Server port */
|
|
44
|
+
port?: number;
|
|
45
|
+
/** Project root directory */
|
|
46
|
+
root?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compile options
|
|
51
|
+
*/
|
|
52
|
+
export interface CompileOptions {
|
|
53
|
+
/** Project root directory */
|
|
54
|
+
root?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Logger instance
|
|
59
|
+
*/
|
|
60
|
+
export const logger: Logger;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Default configuration
|
|
64
|
+
*/
|
|
65
|
+
export const defaultConfig: BertuiConfig;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Load configuration
|
|
69
|
+
*/
|
|
70
|
+
export function loadConfig(root: string): Promise<BertuiConfig>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Start development server
|
|
74
|
+
*/
|
|
75
|
+
export function startDev(options?: DevOptions): Promise<void>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Build for production
|
|
79
|
+
*/
|
|
80
|
+
export function buildProduction(options?: BuildOptions): Promise<void>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Compile project
|
|
84
|
+
*/
|
|
85
|
+
export function compileProject(root: string): Promise<{
|
|
86
|
+
outDir: string;
|
|
87
|
+
stats: { files: number; skipped: number };
|
|
88
|
+
routes: any[];
|
|
89
|
+
}>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* CLI program
|
|
93
|
+
*/
|
|
94
|
+
export function program(): void;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* BertUI version
|
|
98
|
+
*/
|
|
99
|
+
export const version: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Global declarations for Server Islands
|
|
103
|
+
declare global {
|
|
104
|
+
/**
|
|
105
|
+
* Mark a page component as a Server Island (SSG)
|
|
106
|
+
* Add this export to any page to enable static generation:
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```tsx
|
|
110
|
+
* export const render = "server";
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export const render: "server" | "client";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export {};
|
package/types/react.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// bertui/types/router.d.ts
|
|
2
|
+
declare module 'bertui/router' {
|
|
3
|
+
import { ReactNode, ComponentType } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Route parameter object
|
|
7
|
+
*/
|
|
8
|
+
export interface RouteParams {
|
|
9
|
+
[key: string]: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Router context value
|
|
14
|
+
*/
|
|
15
|
+
export interface RouterContext {
|
|
16
|
+
/** Current active route */
|
|
17
|
+
currentRoute: Route | null;
|
|
18
|
+
/** Dynamic route parameters */
|
|
19
|
+
params: RouteParams;
|
|
20
|
+
/** Navigate to a new path */
|
|
21
|
+
navigate: (path: string) => void;
|
|
22
|
+
/** Current pathname */
|
|
23
|
+
pathname: string;
|
|
24
|
+
/** Whether running in SSR mode */
|
|
25
|
+
isSSR?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Route configuration object
|
|
30
|
+
*/
|
|
31
|
+
export interface Route {
|
|
32
|
+
/** Route path (e.g., "/", "/blog", "/user/:id") */
|
|
33
|
+
path: string;
|
|
34
|
+
/** React component to render */
|
|
35
|
+
component: ComponentType<{ params?: RouteParams }>;
|
|
36
|
+
/** Route type: "static" or "dynamic" */
|
|
37
|
+
type: 'static' | 'dynamic';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Router component props
|
|
42
|
+
*/
|
|
43
|
+
export interface RouterProps {
|
|
44
|
+
/** Array of route configurations */
|
|
45
|
+
routes: Route[];
|
|
46
|
+
/** Initial path for SSR */
|
|
47
|
+
initialPath?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Link component props
|
|
52
|
+
*/
|
|
53
|
+
export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
54
|
+
/** Destination path */
|
|
55
|
+
to: string;
|
|
56
|
+
/** Link content */
|
|
57
|
+
children: ReactNode;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Router component for client-side routing
|
|
62
|
+
*/
|
|
63
|
+
export const Router: ComponentType<RouterProps>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Link component for navigation
|
|
67
|
+
*/
|
|
68
|
+
export const Link: ComponentType<LinkProps>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Hook to access router context
|
|
72
|
+
*/
|
|
73
|
+
export function useRouter(): RouterContext;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Exported routes configuration
|
|
77
|
+
*/
|
|
78
|
+
export const routes: Route[];
|
|
79
|
+
}
|