bertui 1.1.1 → 1.1.2
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 +424 -435
- package/package.json +1 -1
- package/src/build/generators/html-generator.js +41 -7
- package/src/build/processors/css-builder.js +71 -4
- package/src/build.js +77 -35
- package/src/client/compiler.js +23 -42
- package/src/pagebuilder/core.js +191 -0
- package/src/server/dev-server.js +134 -36
- package/src/utils/env.js +59 -39
- package/src/utils/meta-extractor.js +124 -58
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// bertui/src/build/generators/html-generator.js
|
|
1
|
+
// bertui/src/build/generators/html-generator.js - FIXED PRODUCTION IMPORT MAP
|
|
2
2
|
import { join, relative } from 'path';
|
|
3
|
-
import { mkdirSync } from 'fs';
|
|
3
|
+
import { mkdirSync, existsSync, cpSync } from 'fs';
|
|
4
4
|
import logger from '../../logger/logger.js';
|
|
5
5
|
import { extractMetaFromSource } from '../../utils/meta-extractor.js';
|
|
6
6
|
|
|
@@ -17,6 +17,9 @@ export async function generateProductionHTML(root, outDir, buildResult, routes,
|
|
|
17
17
|
const bundlePath = relative(outDir, mainBundle.path).replace(/\\/g, '/');
|
|
18
18
|
const defaultMeta = config.meta || {};
|
|
19
19
|
|
|
20
|
+
// ✅ FIX: Check if bertui-icons is installed and copy to dist/
|
|
21
|
+
const bertuiIconsInstalled = await copyBertuiIconsToProduction(root, outDir);
|
|
22
|
+
|
|
20
23
|
logger.info(`📄 Generating HTML for ${routes.length} routes...`);
|
|
21
24
|
|
|
22
25
|
// Process in batches to avoid Bun crashes
|
|
@@ -28,14 +31,39 @@ export async function generateProductionHTML(root, outDir, buildResult, routes,
|
|
|
28
31
|
|
|
29
32
|
// Process batch sequentially
|
|
30
33
|
for (const route of batch) {
|
|
31
|
-
await processSingleRoute(route, serverIslands, config, defaultMeta, bundlePath, outDir);
|
|
34
|
+
await processSingleRoute(route, serverIslands, config, defaultMeta, bundlePath, outDir, bertuiIconsInstalled);
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
logger.success(`✅ HTML generation complete for ${routes.length} routes`);
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
// ✅ NEW: Copy bertui-icons to dist/ for production
|
|
42
|
+
async function copyBertuiIconsToProduction(root, outDir) {
|
|
43
|
+
const nodeModulesDir = join(root, 'node_modules');
|
|
44
|
+
const bertuiIconsSource = join(nodeModulesDir, 'bertui-icons');
|
|
45
|
+
|
|
46
|
+
if (!existsSync(bertuiIconsSource)) {
|
|
47
|
+
logger.debug('bertui-icons not installed, skipping...');
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const bertuiIconsDest = join(outDir, 'node_modules', 'bertui-icons');
|
|
53
|
+
mkdirSync(join(outDir, 'node_modules'), { recursive: true });
|
|
54
|
+
|
|
55
|
+
// Copy the entire bertui-icons package
|
|
56
|
+
cpSync(bertuiIconsSource, bertuiIconsDest, { recursive: true });
|
|
57
|
+
|
|
58
|
+
logger.success('✅ Copied bertui-icons to dist/node_modules/');
|
|
59
|
+
return true;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
logger.error(`Failed to copy bertui-icons: ${error.message}`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function processSingleRoute(route, serverIslands, config, defaultMeta, bundlePath, outDir, bertuiIconsInstalled) {
|
|
39
67
|
try {
|
|
40
68
|
const sourceCode = await Bun.file(route.path).text();
|
|
41
69
|
const pageMeta = extractMetaFromSource(sourceCode);
|
|
@@ -56,7 +84,7 @@ async function processSingleRoute(route, serverIslands, config, defaultMeta, bun
|
|
|
56
84
|
}
|
|
57
85
|
}
|
|
58
86
|
|
|
59
|
-
const html = generateHTML(meta, route, bundlePath, staticHTML, isServerIsland);
|
|
87
|
+
const html = generateHTML(meta, route, bundlePath, staticHTML, isServerIsland, bertuiIconsInstalled);
|
|
60
88
|
|
|
61
89
|
let htmlPath;
|
|
62
90
|
if (route.route === '/') {
|
|
@@ -211,7 +239,8 @@ async function extractStaticHTMLFromComponent(sourceCode, filePath) {
|
|
|
211
239
|
}
|
|
212
240
|
}
|
|
213
241
|
|
|
214
|
-
|
|
242
|
+
// ✅ FIXED: Add bertuiIconsInstalled parameter
|
|
243
|
+
function generateHTML(meta, route, bundlePath, staticHTML = '', isServerIsland = false, bertuiIconsInstalled = false) {
|
|
215
244
|
const rootContent = staticHTML
|
|
216
245
|
? `<div id="root">${staticHTML}</div>`
|
|
217
246
|
: '<div id="root"></div>';
|
|
@@ -220,6 +249,11 @@ function generateHTML(meta, route, bundlePath, staticHTML = '', isServerIsland =
|
|
|
220
249
|
? '<!-- 🏝️ Server Island: Static content rendered at build time -->'
|
|
221
250
|
: '<!-- ⚡ Client-only: Content rendered by JavaScript -->';
|
|
222
251
|
|
|
252
|
+
// ✅ FIX: Add bertui-icons to production import map if installed
|
|
253
|
+
const bertuiIconsImport = bertuiIconsInstalled
|
|
254
|
+
? ',\n "bertui-icons": "/node_modules/bertui-icons/generated/index.js"'
|
|
255
|
+
: '';
|
|
256
|
+
|
|
223
257
|
return `<!DOCTYPE html>
|
|
224
258
|
<html lang="${meta.lang || 'en'}">
|
|
225
259
|
<head>
|
|
@@ -245,7 +279,7 @@ function generateHTML(meta, route, bundlePath, staticHTML = '', isServerIsland =
|
|
|
245
279
|
"react": "https://esm.sh/react@18.2.0",
|
|
246
280
|
"react-dom": "https://esm.sh/react-dom@18.2.0",
|
|
247
281
|
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
|
|
248
|
-
"react/jsx-runtime": "https://esm.sh/react@18.2.0/jsx-runtime"
|
|
282
|
+
"react/jsx-runtime": "https://esm.sh/react@18.2.0/jsx-runtime"${bertuiIconsImport}
|
|
249
283
|
}
|
|
250
284
|
}
|
|
251
285
|
</script>
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
// bertui/src/build/processors/css-builder.js
|
|
1
|
+
// bertui/src/build/processors/css-builder.js - SAFE VERSION
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync, readdirSync, mkdirSync } from 'fs';
|
|
4
4
|
import logger from '../../logger/logger.js';
|
|
5
|
-
import { buildCSS } from '../css-builder.js';
|
|
6
5
|
|
|
7
6
|
export async function buildAllCSS(root, outDir) {
|
|
8
7
|
const srcStylesDir = join(root, 'src', 'styles');
|
|
@@ -18,6 +17,8 @@ export async function buildAllCSS(root, outDir) {
|
|
|
18
17
|
return;
|
|
19
18
|
}
|
|
20
19
|
|
|
20
|
+
logger.info(`Processing ${cssFiles.length} CSS file(s)...`);
|
|
21
|
+
|
|
21
22
|
let combinedCSS = '';
|
|
22
23
|
for (const cssFile of cssFiles) {
|
|
23
24
|
const srcPath = join(srcStylesDir, cssFile);
|
|
@@ -27,9 +28,75 @@ export async function buildAllCSS(root, outDir) {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const combinedPath = join(stylesOutDir, 'bertui.min.css');
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
// ✅ SAFE: Try Lightning CSS, fallback to simple minification
|
|
33
|
+
try {
|
|
34
|
+
const minified = await minifyCSSSafe(combinedCSS);
|
|
35
|
+
await Bun.write(combinedPath, minified);
|
|
36
|
+
|
|
37
|
+
const originalSize = Buffer.byteLength(combinedCSS);
|
|
38
|
+
const minifiedSize = Buffer.byteLength(minified);
|
|
39
|
+
const reduction = ((1 - minifiedSize / originalSize) * 100).toFixed(1);
|
|
40
|
+
|
|
41
|
+
logger.success(`CSS minified: ${(originalSize/1024).toFixed(2)}KB → ${(minifiedSize/1024).toFixed(2)}KB (-${reduction}%)`);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
logger.warn(`CSS minification failed: ${error.message}`);
|
|
44
|
+
logger.info('Falling back to unminified CSS...');
|
|
45
|
+
await Bun.write(combinedPath, combinedCSS);
|
|
46
|
+
}
|
|
32
47
|
|
|
33
48
|
logger.success(`✅ Combined ${cssFiles.length} CSS files`);
|
|
49
|
+
} else {
|
|
50
|
+
// No styles directory, create empty CSS
|
|
51
|
+
await Bun.write(join(stylesOutDir, 'bertui.min.css'), '/* No custom styles */');
|
|
52
|
+
logger.info('No styles directory found, created empty CSS');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Safe CSS minification with fallback
|
|
58
|
+
*/
|
|
59
|
+
async function minifyCSSSafe(css) {
|
|
60
|
+
// Try Lightning CSS first
|
|
61
|
+
try {
|
|
62
|
+
const { transform } = await import('lightningcss');
|
|
63
|
+
|
|
64
|
+
const { code } = transform({
|
|
65
|
+
filename: 'styles.css',
|
|
66
|
+
code: Buffer.from(css),
|
|
67
|
+
minify: true,
|
|
68
|
+
sourceMap: false,
|
|
69
|
+
targets: {
|
|
70
|
+
chrome: 90 << 16,
|
|
71
|
+
firefox: 88 << 16,
|
|
72
|
+
safari: 14 << 16,
|
|
73
|
+
edge: 90 << 16
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return code.toString();
|
|
78
|
+
|
|
79
|
+
} catch (lightningError) {
|
|
80
|
+
logger.warn('Lightning CSS failed, using simple minification');
|
|
81
|
+
|
|
82
|
+
// Fallback: Simple manual minification
|
|
83
|
+
return simpleMinifyCSS(css);
|
|
34
84
|
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Simple CSS minification without dependencies
|
|
89
|
+
*/
|
|
90
|
+
function simpleMinifyCSS(css) {
|
|
91
|
+
return css
|
|
92
|
+
// Remove comments
|
|
93
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
94
|
+
// Remove extra whitespace
|
|
95
|
+
.replace(/\s+/g, ' ')
|
|
96
|
+
// Remove space around { } : ; ,
|
|
97
|
+
.replace(/\s*([{}:;,])\s*/g, '$1')
|
|
98
|
+
// Remove trailing semicolons before }
|
|
99
|
+
.replace(/;}/g, '}')
|
|
100
|
+
// Remove leading/trailing whitespace
|
|
101
|
+
.trim();
|
|
35
102
|
}
|
package/src/build.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
// bertui/src/build.js -
|
|
1
|
+
// bertui/src/build.js - FIXED BUNDLING
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync, mkdirSync, rmSync } from 'fs';
|
|
4
4
|
import logger from './logger/logger.js';
|
|
5
5
|
import { loadEnvVariables } from './utils/env.js';
|
|
6
|
+
import { runPageBuilder } from './pagebuilder/core.js';
|
|
6
7
|
|
|
7
8
|
// Import modular components
|
|
8
9
|
import { compileForBuild } from './build/compiler/index.js';
|
|
@@ -12,8 +13,6 @@ import { generateProductionHTML } from './build/generators/html-generator.js';
|
|
|
12
13
|
import { generateSitemap } from './build/generators/sitemap-generator.js';
|
|
13
14
|
import { generateRobots } from './build/generators/robots-generator.js';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
16
|
export async function buildProduction(options = {}) {
|
|
18
17
|
const root = options.root || process.cwd();
|
|
19
18
|
const buildDir = join(root, '.bertuibuild');
|
|
@@ -35,6 +34,15 @@ export async function buildProduction(options = {}) {
|
|
|
35
34
|
logger.info('Step 0: Loading environment variables...');
|
|
36
35
|
const envVars = loadEnvVariables(root);
|
|
37
36
|
|
|
37
|
+
// Step 0.5: Load config and run Page Builder
|
|
38
|
+
const { loadConfig } = await import('./config/loadConfig.js');
|
|
39
|
+
const config = await loadConfig(root);
|
|
40
|
+
|
|
41
|
+
if (config.pageBuilder) {
|
|
42
|
+
logger.info('Step 0.5: Running Page Builder...');
|
|
43
|
+
await runPageBuilder(root, config);
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
// Step 1: Compilation
|
|
39
47
|
logger.info('Step 1: Compiling and detecting Server Islands...');
|
|
40
48
|
const { routes, serverIslands, clientRoutes } = await compileForBuild(root, buildDir, envVars);
|
|
@@ -59,12 +67,18 @@ export async function buildProduction(options = {}) {
|
|
|
59
67
|
// Step 4: JavaScript Bundling
|
|
60
68
|
logger.info('Step 4: Bundling JavaScript...');
|
|
61
69
|
const buildEntry = join(buildDir, 'main.js');
|
|
70
|
+
|
|
71
|
+
// ✅ CRITICAL FIX: Check if main.js exists before bundling
|
|
72
|
+
if (!existsSync(buildEntry)) {
|
|
73
|
+
logger.error('❌ main.js not found in build directory!');
|
|
74
|
+
logger.error(' Expected: ' + buildEntry);
|
|
75
|
+
throw new Error('Build entry point missing. Compilation may have failed.');
|
|
76
|
+
}
|
|
77
|
+
|
|
62
78
|
const result = await bundleJavaScript(buildEntry, outDir, envVars);
|
|
63
79
|
|
|
64
80
|
// Step 5: HTML Generation
|
|
65
81
|
logger.info('Step 5: Generating HTML with Server Islands...');
|
|
66
|
-
const { loadConfig } = await import('./config/loadConfig.js');
|
|
67
|
-
const config = await loadConfig(root);
|
|
68
82
|
await generateProductionHTML(root, outDir, result, routes, serverIslands, config);
|
|
69
83
|
|
|
70
84
|
// Step 6: Sitemap
|
|
@@ -91,38 +105,66 @@ export async function buildProduction(options = {}) {
|
|
|
91
105
|
}
|
|
92
106
|
|
|
93
107
|
async function bundleJavaScript(buildEntry, outDir, envVars) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
try {
|
|
109
|
+
// ✅ CRITICAL FIX: Better error handling and clearer external configuration
|
|
110
|
+
const result = await Bun.build({
|
|
111
|
+
entrypoints: [buildEntry],
|
|
112
|
+
outdir: join(outDir, 'assets'),
|
|
113
|
+
target: 'browser',
|
|
114
|
+
minify: true,
|
|
115
|
+
splitting: true,
|
|
116
|
+
sourcemap: 'external',
|
|
117
|
+
naming: {
|
|
118
|
+
entry: '[name]-[hash].js',
|
|
119
|
+
chunk: 'chunks/[name]-[hash].js',
|
|
120
|
+
asset: '[name]-[hash].[ext]'
|
|
121
|
+
},
|
|
122
|
+
// ✅ FIXED: Externalize React to use CDN (reduces bundle size)
|
|
123
|
+
external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime'],
|
|
124
|
+
define: {
|
|
125
|
+
'process.env.NODE_ENV': '"production"',
|
|
126
|
+
...Object.fromEntries(
|
|
127
|
+
Object.entries(envVars).map(([key, value]) => [
|
|
128
|
+
`process.env.${key}`,
|
|
129
|
+
JSON.stringify(value)
|
|
130
|
+
])
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!result.success) {
|
|
136
|
+
logger.error('❌ JavaScript build failed!');
|
|
137
|
+
|
|
138
|
+
// ✅ IMPROVED: Better error reporting
|
|
139
|
+
if (result.logs && result.logs.length > 0) {
|
|
140
|
+
logger.error('\n📋 Build errors:');
|
|
141
|
+
result.logs.forEach((log, i) => {
|
|
142
|
+
logger.error(`\n${i + 1}. ${log.message}`);
|
|
143
|
+
if (log.position) {
|
|
144
|
+
logger.error(` File: ${log.position.file || 'unknown'}`);
|
|
145
|
+
logger.error(` Line: ${log.position.line || 'unknown'}`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw new Error('JavaScript bundling failed - check errors above');
|
|
115
151
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
logger.
|
|
120
|
-
result.
|
|
121
|
-
|
|
152
|
+
|
|
153
|
+
// ✅ IMPROVED: Log successful bundle info
|
|
154
|
+
logger.success('✅ JavaScript bundled successfully');
|
|
155
|
+
logger.info(` Entry points: ${result.outputs.filter(o => o.kind === 'entry-point').length}`);
|
|
156
|
+
logger.info(` Chunks: ${result.outputs.filter(o => o.kind === 'chunk').length}`);
|
|
157
|
+
|
|
158
|
+
const totalSize = result.outputs.reduce((sum, o) => sum + (o.size || 0), 0);
|
|
159
|
+
logger.info(` Total size: ${(totalSize / 1024).toFixed(2)} KB`);
|
|
160
|
+
|
|
161
|
+
return result;
|
|
162
|
+
|
|
163
|
+
} catch (error) {
|
|
164
|
+
logger.error('❌ Bundling error: ' + error.message);
|
|
165
|
+
if (error.stack) logger.error(error.stack);
|
|
166
|
+
throw error;
|
|
122
167
|
}
|
|
123
|
-
|
|
124
|
-
logger.success('JavaScript bundled');
|
|
125
|
-
return result;
|
|
126
168
|
}
|
|
127
169
|
|
|
128
170
|
function showBuildSummary(routes, serverIslands, clientRoutes, duration) {
|
package/src/client/compiler.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// bertui/src/client/compiler.js - FIXED NODE_MODULES IMPORTS
|
|
1
2
|
import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
2
3
|
import { join, extname, relative, dirname } from 'path';
|
|
3
4
|
import logger from '../logger/logger.js';
|
|
@@ -73,12 +74,10 @@ async function discoverRoutes(pagesDir) {
|
|
|
73
74
|
await scanDirectory(fullPath, relativePath);
|
|
74
75
|
} else if (entry.isFile()) {
|
|
75
76
|
const ext = extname(entry.name);
|
|
76
|
-
|
|
77
77
|
if (ext === '.css') continue;
|
|
78
78
|
|
|
79
79
|
if (['.jsx', '.tsx', '.js', '.ts'].includes(ext)) {
|
|
80
80
|
const fileName = entry.name.replace(ext, '');
|
|
81
|
-
|
|
82
81
|
let route = '/' + relativePath.replace(/\\/g, '/').replace(ext, '');
|
|
83
82
|
|
|
84
83
|
if (fileName === 'index') {
|
|
@@ -100,7 +99,6 @@ async function discoverRoutes(pagesDir) {
|
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
await scanDirectory(pagesDir);
|
|
103
|
-
|
|
104
102
|
routes.sort((a, b) => {
|
|
105
103
|
if (a.type === b.type) {
|
|
106
104
|
return a.route.localeCompare(b.route);
|
|
@@ -141,11 +139,7 @@ export function Router({ routes }) {
|
|
|
141
139
|
|
|
142
140
|
useEffect(() => {
|
|
143
141
|
matchAndSetRoute(window.location.pathname);
|
|
144
|
-
|
|
145
|
-
const handlePopState = () => {
|
|
146
|
-
matchAndSetRoute(window.location.pathname);
|
|
147
|
-
};
|
|
148
|
-
|
|
142
|
+
const handlePopState = () => matchAndSetRoute(window.location.pathname);
|
|
149
143
|
window.addEventListener('popstate', handlePopState);
|
|
150
144
|
return () => window.removeEventListener('popstate', handlePopState);
|
|
151
145
|
}, [routes]);
|
|
@@ -158,27 +152,21 @@ export function Router({ routes }) {
|
|
|
158
152
|
return;
|
|
159
153
|
}
|
|
160
154
|
}
|
|
161
|
-
|
|
162
155
|
for (const route of routes) {
|
|
163
156
|
if (route.type === 'dynamic') {
|
|
164
157
|
const pattern = route.path.replace(/\\[([^\\]]+)\\]/g, '([^/]+)');
|
|
165
158
|
const regex = new RegExp('^' + pattern + '$');
|
|
166
159
|
const match = pathname.match(regex);
|
|
167
|
-
|
|
168
160
|
if (match) {
|
|
169
161
|
const paramNames = [...route.path.matchAll(/\\[([^\\]]+)\\]/g)].map(m => m[1]);
|
|
170
162
|
const extractedParams = {};
|
|
171
|
-
paramNames.forEach((name, i) => {
|
|
172
|
-
extractedParams[name] = match[i + 1];
|
|
173
|
-
});
|
|
174
|
-
|
|
163
|
+
paramNames.forEach((name, i) => { extractedParams[name] = match[i + 1]; });
|
|
175
164
|
setCurrentRoute(route);
|
|
176
165
|
setParams(extractedParams);
|
|
177
166
|
return;
|
|
178
167
|
}
|
|
179
168
|
}
|
|
180
169
|
}
|
|
181
|
-
|
|
182
170
|
setCurrentRoute(null);
|
|
183
171
|
setParams({});
|
|
184
172
|
}
|
|
@@ -188,31 +176,21 @@ export function Router({ routes }) {
|
|
|
188
176
|
matchAndSetRoute(path);
|
|
189
177
|
}
|
|
190
178
|
|
|
191
|
-
const routerValue = {
|
|
192
|
-
currentRoute,
|
|
193
|
-
params,
|
|
194
|
-
navigate,
|
|
195
|
-
pathname: window.location.pathname
|
|
196
|
-
};
|
|
197
|
-
|
|
198
179
|
const Component = currentRoute?.component;
|
|
199
|
-
|
|
200
180
|
return React.createElement(
|
|
201
181
|
RouterContext.Provider,
|
|
202
|
-
{ value:
|
|
203
|
-
Component ? React.createElement(Component, { params }) : React.createElement(NotFound
|
|
182
|
+
{ value: { currentRoute, params, navigate, pathname: window.location.pathname } },
|
|
183
|
+
Component ? React.createElement(Component, { params }) : React.createElement(NotFound)
|
|
204
184
|
);
|
|
205
185
|
}
|
|
206
186
|
|
|
207
187
|
export function Link({ to, children, ...props }) {
|
|
208
188
|
const { navigate } = useRouter();
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
e.preventDefault();
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return React.createElement('a', { href: to, onClick: handleClick, ...props }, children);
|
|
189
|
+
return React.createElement('a', {
|
|
190
|
+
href: to,
|
|
191
|
+
onClick: (e) => { e.preventDefault(); navigate(to); },
|
|
192
|
+
...props
|
|
193
|
+
}, children);
|
|
216
194
|
}
|
|
217
195
|
|
|
218
196
|
function NotFound() {
|
|
@@ -250,7 +228,6 @@ ${routeConfigs}
|
|
|
250
228
|
|
|
251
229
|
async function compileDirectory(srcDir, outDir, root, envVars) {
|
|
252
230
|
const stats = { files: 0, skipped: 0 };
|
|
253
|
-
|
|
254
231
|
const files = readdirSync(srcDir);
|
|
255
232
|
|
|
256
233
|
for (const file of files) {
|
|
@@ -287,7 +264,6 @@ async function compileDirectory(srcDir, outDir, root, envVars) {
|
|
|
287
264
|
code = replaceEnvInCode(code, envVars);
|
|
288
265
|
code = fixRouterImports(code, outPath, root);
|
|
289
266
|
|
|
290
|
-
// ✅ CRITICAL FIX: Ensure React import for .js files with JSX
|
|
291
267
|
if (usesJSX(code) && !code.includes('import React')) {
|
|
292
268
|
code = `import React from 'react';\n${code}`;
|
|
293
269
|
}
|
|
@@ -331,11 +307,11 @@ async function compileFile(srcPath, outDir, filename, relativePath, root, envVar
|
|
|
331
307
|
});
|
|
332
308
|
let compiled = await transpiler.transform(code);
|
|
333
309
|
|
|
334
|
-
// ✅ CRITICAL FIX: Always add React import if JSX is present
|
|
335
310
|
if (usesJSX(compiled) && !compiled.includes('import React')) {
|
|
336
311
|
compiled = `import React from 'react';\n${compiled}`;
|
|
337
312
|
}
|
|
338
313
|
|
|
314
|
+
// ✅ CRITICAL FIX: Don't touch node_modules imports
|
|
339
315
|
compiled = fixRelativeImports(compiled);
|
|
340
316
|
|
|
341
317
|
await Bun.write(outPath, compiled);
|
|
@@ -346,13 +322,12 @@ async function compileFile(srcPath, outDir, filename, relativePath, root, envVar
|
|
|
346
322
|
}
|
|
347
323
|
}
|
|
348
324
|
|
|
349
|
-
// ✅ NEW: Detect if code uses JSX
|
|
350
325
|
function usesJSX(code) {
|
|
351
326
|
return code.includes('React.createElement') ||
|
|
352
327
|
code.includes('React.Fragment') ||
|
|
353
|
-
/<[A-Z]/.test(code) ||
|
|
354
|
-
code.includes('jsx(') ||
|
|
355
|
-
code.includes('jsxs(');
|
|
328
|
+
/<[A-Z]/.test(code) ||
|
|
329
|
+
code.includes('jsx(') ||
|
|
330
|
+
code.includes('jsxs(');
|
|
356
331
|
}
|
|
357
332
|
|
|
358
333
|
function removeCSSImports(code) {
|
|
@@ -384,13 +359,19 @@ function fixRouterImports(code, outPath, root) {
|
|
|
384
359
|
}
|
|
385
360
|
|
|
386
361
|
function fixRelativeImports(code) {
|
|
387
|
-
|
|
362
|
+
// ✅ CRITICAL FIX: Only fix relative imports, NOT bare specifiers like 'bertui-icons'
|
|
363
|
+
// Regex explanation:
|
|
364
|
+
// - Match: from './file' or from '../file'
|
|
365
|
+
// - DON'T match: from 'bertui-icons' or from 'react'
|
|
366
|
+
|
|
367
|
+
const importRegex = /from\s+['"](\.\.?\/[^'"]+?)(?<!\.js|\.jsx|\.ts|\.tsx|\.json)['"]/g;
|
|
388
368
|
|
|
389
|
-
code = code.replace(importRegex, (match,
|
|
369
|
+
code = code.replace(importRegex, (match, path) => {
|
|
370
|
+
// Don't add .js if path already has an extension or ends with /
|
|
390
371
|
if (path.endsWith('/') || /\.\w+$/.test(path)) {
|
|
391
372
|
return match;
|
|
392
373
|
}
|
|
393
|
-
return `from '${
|
|
374
|
+
return `from '${path}.js'`;
|
|
394
375
|
});
|
|
395
376
|
|
|
396
377
|
return code;
|