bertui 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +551 -132
- package/index.js +1 -1
- package/package.json +2 -2
- package/src/build/server-island-validator.js +156 -0
- package/src/build.js +468 -366
- package/src/router/Router.js +38 -5
- package/src/router/SSRRouter.js +156 -0
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bertui",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Lightning-fast React dev server powered by Bun and Elysia",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "^18.0.0 || ^19.0.0",
|
|
53
|
-
"react-dom": "^
|
|
53
|
+
"react-dom": "^19.2.3",
|
|
54
54
|
"bun": ">=1.0.0"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// bertui/src/build/server-island-validator.js
|
|
2
|
+
// Fixed validation for Server Islands - no false positives!
|
|
3
|
+
|
|
4
|
+
import logger from '../logger/logger.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validates that a Server Island component follows all rules
|
|
8
|
+
* @param {string} sourceCode - The component source code
|
|
9
|
+
* @param {string} filePath - Path to the file (for error messages)
|
|
10
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
11
|
+
*/
|
|
12
|
+
export function validateServerIsland(sourceCode, filePath) {
|
|
13
|
+
const errors = [];
|
|
14
|
+
|
|
15
|
+
// Rule 1: No React hooks (FIXED: only match actual function calls)
|
|
16
|
+
const hookPatterns = [
|
|
17
|
+
'useState',
|
|
18
|
+
'useEffect',
|
|
19
|
+
'useContext',
|
|
20
|
+
'useReducer',
|
|
21
|
+
'useCallback',
|
|
22
|
+
'useMemo',
|
|
23
|
+
'useRef',
|
|
24
|
+
'useImperativeHandle',
|
|
25
|
+
'useLayoutEffect',
|
|
26
|
+
'useDebugValue',
|
|
27
|
+
'useId',
|
|
28
|
+
'useDeferredValue',
|
|
29
|
+
'useTransition',
|
|
30
|
+
'useSyncExternalStore'
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const hook of hookPatterns) {
|
|
34
|
+
// FIXED: Only match hooks as function calls, not in text/comments
|
|
35
|
+
// Looks for: useState( or const [x] = useState(
|
|
36
|
+
const regex = new RegExp(`\\b${hook}\\s*\\(`, 'g');
|
|
37
|
+
if (regex.test(sourceCode)) {
|
|
38
|
+
errors.push(`❌ Uses React hook: ${hook}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Rule 2: No bertui/router imports
|
|
43
|
+
if (sourceCode.includes('from \'bertui/router\'') ||
|
|
44
|
+
sourceCode.includes('from "bertui/router"')) {
|
|
45
|
+
errors.push('❌ Imports from \'bertui/router\' (use <a> tags instead of Link)');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Rule 3: No browser APIs (FIXED: only match actual usage, not in strings/comments)
|
|
49
|
+
const browserAPIs = [
|
|
50
|
+
{ pattern: 'window\\.', name: 'window' },
|
|
51
|
+
{ pattern: 'document\\.', name: 'document' },
|
|
52
|
+
{ pattern: 'localStorage\\.', name: 'localStorage' },
|
|
53
|
+
{ pattern: 'sessionStorage\\.', name: 'sessionStorage' },
|
|
54
|
+
{ pattern: 'navigator\\.', name: 'navigator' },
|
|
55
|
+
{ pattern: 'location\\.', name: 'location' },
|
|
56
|
+
{ pattern: 'history\\.', name: 'history' },
|
|
57
|
+
{ pattern: '(?<!//.*|/\\*.*|\\*)\\bfetch\\s*\\(', name: 'fetch' },
|
|
58
|
+
{ pattern: '\\.addEventListener\\s*\\(', name: 'addEventListener' },
|
|
59
|
+
{ pattern: '\\.removeEventListener\\s*\\(', name: 'removeEventListener' },
|
|
60
|
+
{ pattern: '\\bsetTimeout\\s*\\(', name: 'setTimeout' },
|
|
61
|
+
{ pattern: '\\bsetInterval\\s*\\(', name: 'setInterval' },
|
|
62
|
+
{ pattern: '\\brequestAnimationFrame\\s*\\(', name: 'requestAnimationFrame' }
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
for (const api of browserAPIs) {
|
|
66
|
+
const regex = new RegExp(api.pattern, 'g');
|
|
67
|
+
if (regex.test(sourceCode)) {
|
|
68
|
+
if (api.name === 'console') {
|
|
69
|
+
logger.warn(`⚠️ ${filePath} uses console.log (will not work in static HTML)`);
|
|
70
|
+
} else {
|
|
71
|
+
errors.push(`❌ Uses browser API: ${api.name}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Rule 4: No event handlers (these won't work without JS)
|
|
77
|
+
const eventHandlers = [
|
|
78
|
+
'onClick=',
|
|
79
|
+
'onChange=',
|
|
80
|
+
'onSubmit=',
|
|
81
|
+
'onInput=',
|
|
82
|
+
'onFocus=',
|
|
83
|
+
'onBlur=',
|
|
84
|
+
'onMouseEnter=',
|
|
85
|
+
'onMouseLeave=',
|
|
86
|
+
'onKeyDown=',
|
|
87
|
+
'onKeyUp=',
|
|
88
|
+
'onScroll='
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
for (const handler of eventHandlers) {
|
|
92
|
+
if (sourceCode.includes(handler)) {
|
|
93
|
+
errors.push(`❌ Uses event handler: ${handler.replace('=', '')} (Server Islands are static HTML)`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Rule 5: Check for dynamic imports
|
|
98
|
+
if (/import\s*\(/.test(sourceCode)) {
|
|
99
|
+
errors.push('❌ Uses dynamic import() (not supported in Server Islands)');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Rule 6: Check for async/await (usually indicates API calls)
|
|
103
|
+
if (/async\s+function|async\s*\(|async\s+\w+\s*\(/.test(sourceCode)) {
|
|
104
|
+
errors.push('❌ Uses async/await (Server Islands must be synchronous)');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const valid = errors.length === 0;
|
|
108
|
+
|
|
109
|
+
return { valid, errors };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Display validation errors in a clear format
|
|
114
|
+
*/
|
|
115
|
+
export function displayValidationErrors(filePath, errors) {
|
|
116
|
+
logger.error(`\n🏝️ Server Island validation failed: ${filePath}`);
|
|
117
|
+
logger.error('\nViolations:');
|
|
118
|
+
errors.forEach(error => logger.error(` ${error}`));
|
|
119
|
+
logger.error('\n📖 Server Island Rules:');
|
|
120
|
+
logger.error(' ✅ Pure static JSX only');
|
|
121
|
+
logger.error(' ❌ No React hooks (useState, useEffect, etc.)');
|
|
122
|
+
logger.error(' ❌ No Link component (use <a> tags)');
|
|
123
|
+
logger.error(' ❌ No browser APIs (window, document, fetch)');
|
|
124
|
+
logger.error(' ❌ No event handlers (onClick, onChange, etc.)');
|
|
125
|
+
logger.error('\n💡 Tip: Remove the "export const render = \\"server\\"" line');
|
|
126
|
+
logger.error(' if you need these features (page will be client-only).\n');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Extract and validate all Server Islands in a project
|
|
131
|
+
*/
|
|
132
|
+
export async function validateAllServerIslands(routes) {
|
|
133
|
+
const serverIslands = [];
|
|
134
|
+
const validationResults = [];
|
|
135
|
+
|
|
136
|
+
for (const route of routes) {
|
|
137
|
+
const sourceCode = await Bun.file(route.path).text();
|
|
138
|
+
const isServerIsland = sourceCode.includes('export const render = "server"');
|
|
139
|
+
|
|
140
|
+
if (isServerIsland) {
|
|
141
|
+
const validation = validateServerIsland(sourceCode, route.path);
|
|
142
|
+
|
|
143
|
+
validationResults.push({
|
|
144
|
+
route: route.route,
|
|
145
|
+
path: route.path,
|
|
146
|
+
...validation
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (validation.valid) {
|
|
150
|
+
serverIslands.push(route);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { serverIslands, validationResults };
|
|
156
|
+
}
|