bertui 0.2.0 → 0.2.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/index.js +1 -1
- package/package.json +1 -1
- package/src/client/compiler.js +145 -39
package/index.js
CHANGED
package/package.json
CHANGED
package/src/client/compiler.js
CHANGED
|
@@ -40,7 +40,6 @@ export async function compileProject(root) {
|
|
|
40
40
|
const stats = await compileDirectory(srcDir, outDir, root);
|
|
41
41
|
const duration = Date.now() - startTime;
|
|
42
42
|
|
|
43
|
-
// Generate router AFTER compilation
|
|
44
43
|
if (routes.length > 0) {
|
|
45
44
|
await generateRouter(routes, outDir, root);
|
|
46
45
|
logger.info('Generated router.js');
|
|
@@ -113,44 +112,132 @@ async function generateRouter(routes, outDir, root) {
|
|
|
113
112
|
return ` { path: '${route.route}', component: ${componentName}, type: '${route.type}' }`;
|
|
114
113
|
}).join(',\n');
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
// CRITICAL: Copy Router component into compiled folder
|
|
116
|
+
const routerComponentCode = `
|
|
117
|
+
import { useState, useEffect, createContext, useContext } from 'react';
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
${routeConfigs}
|
|
121
|
-
];
|
|
119
|
+
const RouterContext = createContext(null);
|
|
122
120
|
|
|
123
|
-
export function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
121
|
+
export function useRouter() {
|
|
122
|
+
const context = useContext(RouterContext);
|
|
123
|
+
if (!context) {
|
|
124
|
+
throw new Error('useRouter must be used within a Router component');
|
|
128
125
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
126
|
+
return context;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function Router({ routes }) {
|
|
130
|
+
const [currentRoute, setCurrentRoute] = useState(null);
|
|
131
|
+
const [params, setParams] = useState({});
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
matchAndSetRoute(window.location.pathname);
|
|
135
|
+
|
|
136
|
+
const handlePopState = () => {
|
|
137
|
+
matchAndSetRoute(window.location.pathname);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
window.addEventListener('popstate', handlePopState);
|
|
141
|
+
return () => window.removeEventListener('popstate', handlePopState);
|
|
142
|
+
}, []);
|
|
143
|
+
|
|
144
|
+
function matchAndSetRoute(pathname) {
|
|
145
|
+
for (const route of routes) {
|
|
146
|
+
if (route.type === 'static' && route.path === pathname) {
|
|
147
|
+
setCurrentRoute(route);
|
|
148
|
+
setParams({});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const route of routes) {
|
|
154
|
+
if (route.type === 'dynamic') {
|
|
155
|
+
const pattern = route.path.replace(/\\[([^\\]]+)\\]/g, '([^/]+)');
|
|
156
|
+
const regex = new RegExp('^' + pattern + '$');
|
|
157
|
+
const match = pathname.match(regex);
|
|
158
|
+
|
|
159
|
+
if (match) {
|
|
160
|
+
const paramNames = [...route.path.matchAll(/\\[([^\\]]+)\\]/g)].map(m => m[1]);
|
|
161
|
+
const extractedParams = {};
|
|
162
|
+
paramNames.forEach((name, i) => {
|
|
163
|
+
extractedParams[name] = match[i + 1];
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
setCurrentRoute(route);
|
|
167
|
+
setParams(extractedParams);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
144
170
|
}
|
|
145
171
|
}
|
|
172
|
+
|
|
173
|
+
setCurrentRoute(null);
|
|
174
|
+
setParams({});
|
|
146
175
|
}
|
|
147
|
-
|
|
148
|
-
|
|
176
|
+
|
|
177
|
+
function navigate(path) {
|
|
178
|
+
window.history.pushState({}, '', path);
|
|
179
|
+
matchAndSetRoute(path);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const routerValue = {
|
|
183
|
+
currentRoute,
|
|
184
|
+
params,
|
|
185
|
+
navigate,
|
|
186
|
+
pathname: window.location.pathname
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const Component = currentRoute?.component;
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<RouterContext.Provider value={routerValue}>
|
|
193
|
+
{Component ? <Component params={params} /> : <NotFound />}
|
|
194
|
+
</RouterContext.Provider>
|
|
195
|
+
);
|
|
149
196
|
}
|
|
197
|
+
|
|
198
|
+
export function Link({ to, children, ...props }) {
|
|
199
|
+
const { navigate } = useRouter();
|
|
200
|
+
|
|
201
|
+
function handleClick(e) {
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
navigate(to);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<a href={to} onClick={handleClick} {...props}>
|
|
208
|
+
{children}
|
|
209
|
+
</a>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function NotFound() {
|
|
214
|
+
return (
|
|
215
|
+
<div style={{
|
|
216
|
+
display: 'flex',
|
|
217
|
+
flexDirection: 'column',
|
|
218
|
+
alignItems: 'center',
|
|
219
|
+
justifyContent: 'center',
|
|
220
|
+
minHeight: '100vh',
|
|
221
|
+
fontFamily: 'system-ui'
|
|
222
|
+
}}>
|
|
223
|
+
<h1 style={{ fontSize: '6rem', margin: 0 }}>404</h1>
|
|
224
|
+
<p style={{ fontSize: '1.5rem', color: '#666' }}>Page not found</p>
|
|
225
|
+
<a href="/" style={{ color: '#10b981', textDecoration: 'none', fontSize: '1.2rem' }}>
|
|
226
|
+
Go home
|
|
227
|
+
</a>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
${imports}
|
|
233
|
+
|
|
234
|
+
export const routes = [
|
|
235
|
+
${routeConfigs}
|
|
236
|
+
];
|
|
150
237
|
`;
|
|
151
238
|
|
|
152
239
|
const routerPath = join(outDir, 'router.js');
|
|
153
|
-
await Bun.write(routerPath,
|
|
240
|
+
await Bun.write(routerPath, routerComponentCode);
|
|
154
241
|
}
|
|
155
242
|
|
|
156
243
|
async function compileDirectory(srcDir, outDir, root) {
|
|
@@ -175,9 +262,14 @@ async function compileDirectory(srcDir, outDir, root) {
|
|
|
175
262
|
if (['.jsx', '.tsx', '.ts'].includes(ext)) {
|
|
176
263
|
await compileFile(srcPath, outDir, file, relativePath);
|
|
177
264
|
stats.files++;
|
|
178
|
-
} else if (ext === '.js'
|
|
265
|
+
} else if (ext === '.js') {
|
|
179
266
|
const outPath = join(outDir, file);
|
|
180
|
-
await Bun.
|
|
267
|
+
let code = await Bun.file(srcPath).text();
|
|
268
|
+
|
|
269
|
+
// Fix imports in .js files too
|
|
270
|
+
code = fixImports(code);
|
|
271
|
+
|
|
272
|
+
await Bun.write(outPath, code);
|
|
181
273
|
logger.debug(`Copied: ${relativePath}`);
|
|
182
274
|
stats.files++;
|
|
183
275
|
} else {
|
|
@@ -197,13 +289,13 @@ async function compileFile(srcPath, outDir, filename, relativePath) {
|
|
|
197
289
|
try {
|
|
198
290
|
let code = await Bun.file(srcPath).text();
|
|
199
291
|
|
|
200
|
-
//
|
|
201
|
-
code = code
|
|
292
|
+
// Fix imports BEFORE transpiling
|
|
293
|
+
code = fixImports(code);
|
|
202
294
|
|
|
203
295
|
const transpiler = new Bun.Transpiler({ loader });
|
|
204
296
|
let compiled = await transpiler.transform(code);
|
|
205
297
|
|
|
206
|
-
//
|
|
298
|
+
// Add .js extensions to relative imports
|
|
207
299
|
compiled = fixRelativeImports(compiled);
|
|
208
300
|
|
|
209
301
|
const outFilename = filename.replace(/\.(jsx|tsx|ts)$/, '.js');
|
|
@@ -217,15 +309,29 @@ async function compileFile(srcPath, outDir, filename, relativePath) {
|
|
|
217
309
|
}
|
|
218
310
|
}
|
|
219
311
|
|
|
220
|
-
function
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
// But NOT: import X from './path.js' or import X from 'package'
|
|
312
|
+
function fixImports(code) {
|
|
313
|
+
// Remove bertui/styles imports
|
|
314
|
+
code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
|
|
224
315
|
|
|
316
|
+
// Replace bertui/router with /compiled/router.js
|
|
317
|
+
code = code.replace(
|
|
318
|
+
/from\s+['"]bertui\/router['"]/g,
|
|
319
|
+
"from '/compiled/router.js'"
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// Fix ../.bertui/compiled paths to /compiled
|
|
323
|
+
code = code.replace(
|
|
324
|
+
/from\s+['"]\.\.\/\.bertui\/compiled\/([^'"]+)['"]/g,
|
|
325
|
+
"from '/compiled/$1'"
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
return code;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function fixRelativeImports(code) {
|
|
225
332
|
const importRegex = /from\s+['"](\.\.[\/\\]|\.\/)((?:[^'"]+?)(?<!\.js|\.jsx|\.ts|\.tsx|\.json))['"];?/g;
|
|
226
333
|
|
|
227
334
|
code = code.replace(importRegex, (match, prefix, path) => {
|
|
228
|
-
// Don't add .js if path already has an extension or ends with /
|
|
229
335
|
if (path.endsWith('/') || /\.\w+$/.test(path)) {
|
|
230
336
|
return match;
|
|
231
337
|
}
|