bertui 1.2.8 → 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 +45 -196
- 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 -151
- 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 -12
- package/src/build.js +0 -266
- 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
package/src/client/compiler.js
DELETED
|
@@ -1,522 +0,0 @@
|
|
|
1
|
-
// bertui/src/client/compiler.js - WITH IMPORTHOW ALIAS SUPPORT
|
|
2
|
-
import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
3
|
-
import { join, extname, relative, dirname } from 'path';
|
|
4
|
-
import { transform } from 'lightningcss';
|
|
5
|
-
import logger from '../logger/logger.js';
|
|
6
|
-
import { loadEnvVariables, generateEnvCode, replaceEnvInCode } from '../utils/env.js';
|
|
7
|
-
import { buildAliasMap, rewriteAliasImports, getAliasDirs } from '../utils/importhow.js';
|
|
8
|
-
|
|
9
|
-
export async function compileProject(root) {
|
|
10
|
-
logger.bigLog('COMPILING PROJECT', { color: 'blue' });
|
|
11
|
-
|
|
12
|
-
const srcDir = join(root, 'src');
|
|
13
|
-
const pagesDir = join(srcDir, 'pages');
|
|
14
|
-
const outDir = join(root, '.bertui', 'compiled');
|
|
15
|
-
|
|
16
|
-
if (!existsSync(srcDir)) {
|
|
17
|
-
logger.error('src/ directory not found!');
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (!existsSync(outDir)) {
|
|
22
|
-
mkdirSync(outDir, { recursive: true });
|
|
23
|
-
logger.info('Created .bertui/compiled/');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const envVars = loadEnvVariables(root);
|
|
27
|
-
if (Object.keys(envVars).length > 0) {
|
|
28
|
-
logger.info(`Loaded ${Object.keys(envVars).length} environment variables`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const envCode = generateEnvCode(envVars);
|
|
32
|
-
await Bun.write(join(outDir, 'env.js'), envCode);
|
|
33
|
-
|
|
34
|
-
// ── Load config for importhow ────────────────────────────────────────────
|
|
35
|
-
let importhow = {};
|
|
36
|
-
try {
|
|
37
|
-
const { loadConfig } = await import('../config/loadConfig.js');
|
|
38
|
-
const config = await loadConfig(root);
|
|
39
|
-
importhow = config.importhow || {};
|
|
40
|
-
} catch (_) {}
|
|
41
|
-
|
|
42
|
-
const aliasMap = buildAliasMap(importhow, root, outDir);
|
|
43
|
-
|
|
44
|
-
if (aliasMap.size > 0) {
|
|
45
|
-
logger.info(`🔗 importhow: ${aliasMap.size} alias(es) active`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ── Discover routes ──────────────────────────────────────────────────────
|
|
49
|
-
let routes = [];
|
|
50
|
-
if (existsSync(pagesDir)) {
|
|
51
|
-
routes = await discoverRoutes(pagesDir);
|
|
52
|
-
logger.info(`Discovered ${routes.length} routes`);
|
|
53
|
-
|
|
54
|
-
if (routes.length > 0) {
|
|
55
|
-
logger.bigLog('ROUTES DISCOVERED', { color: 'blue' });
|
|
56
|
-
logger.table(routes.map((r, i) => ({
|
|
57
|
-
'': i, route: r.route, file: r.file, type: r.type
|
|
58
|
-
})));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ── Compile src/ ─────────────────────────────────────────────────────────
|
|
63
|
-
const startTime = Date.now();
|
|
64
|
-
const stats = await compileDirectory(srcDir, outDir, root, envVars, aliasMap);
|
|
65
|
-
|
|
66
|
-
// ── Compile alias dirs (importhow targets) ───────────────────────────────
|
|
67
|
-
// NOTE: use raw importhow config here, NOT aliasMap
|
|
68
|
-
// aliasMap resolves to output dirs (for rewriting) — we need source dirs for compilation
|
|
69
|
-
for (const [alias, relPath] of Object.entries(importhow)) {
|
|
70
|
-
const absSrcDir = join(root, relPath);
|
|
71
|
-
if (!existsSync(absSrcDir)) {
|
|
72
|
-
logger.warn(`⚠️ importhow alias "${alias}" points to missing dir: ${absSrcDir}`);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
const aliasOutDir = join(outDir, alias);
|
|
76
|
-
mkdirSync(aliasOutDir, { recursive: true });
|
|
77
|
-
logger.info(`📦 Compiling alias [${alias}] → ${aliasOutDir}`);
|
|
78
|
-
const aliasStats = await compileDirectory(absSrcDir, aliasOutDir, root, envVars, aliasMap);
|
|
79
|
-
stats.files += aliasStats.files;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const duration = Date.now() - startTime;
|
|
83
|
-
|
|
84
|
-
if (routes.length > 0) {
|
|
85
|
-
await generateRouter(routes, outDir, root);
|
|
86
|
-
logger.info('Generated router.js');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
logger.success(`Compiled ${stats.files} files in ${duration}ms`);
|
|
90
|
-
logger.info(`Output: ${outDir}`);
|
|
91
|
-
|
|
92
|
-
return { outDir, stats, routes };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function compileFile(srcPath, root) {
|
|
96
|
-
const srcDir = join(root, 'src');
|
|
97
|
-
const outDir = join(root, '.bertui', 'compiled');
|
|
98
|
-
const envVars = loadEnvVariables(root);
|
|
99
|
-
const ext = extname(srcPath);
|
|
100
|
-
|
|
101
|
-
let importhow = {};
|
|
102
|
-
try {
|
|
103
|
-
const { loadConfig } = await import('../config/loadConfig.js');
|
|
104
|
-
const config = await loadConfig(root);
|
|
105
|
-
importhow = config.importhow || {};
|
|
106
|
-
} catch (_) {}
|
|
107
|
-
|
|
108
|
-
const aliasMap = buildAliasMap(importhow, root, outDir);
|
|
109
|
-
|
|
110
|
-
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
111
|
-
|
|
112
|
-
if (srcPath.endsWith('.module.css')) {
|
|
113
|
-
await compileCSSModule(srcPath, root);
|
|
114
|
-
return { success: true };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (['.jsx', '.tsx', '.ts'].includes(ext)) {
|
|
118
|
-
const fileName = srcPath.split('/').pop();
|
|
119
|
-
const relativePath = relative(srcDir, srcPath);
|
|
120
|
-
await compileFileInternal(srcPath, outDir, fileName, relativePath, root, envVars, aliasMap);
|
|
121
|
-
return {
|
|
122
|
-
outputPath: relativePath.replace(/\.(jsx|tsx|ts)$/, '.js'),
|
|
123
|
-
success: true
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (ext === '.js') {
|
|
128
|
-
const fileName = srcPath.split('/').pop();
|
|
129
|
-
const outPath = join(outDir, fileName);
|
|
130
|
-
let code = await Bun.file(srcPath).text();
|
|
131
|
-
code = transformCSSModuleImports(code, srcPath, root);
|
|
132
|
-
code = removePlainCSSImports(code);
|
|
133
|
-
code = replaceEnvInCode(code, envVars);
|
|
134
|
-
code = fixRouterImports(code, outPath, root);
|
|
135
|
-
code = rewriteAliasImports(code, outPath, aliasMap);
|
|
136
|
-
if (usesJSX(code) && !code.includes('import React')) {
|
|
137
|
-
code = `import React from 'react';\n${code}`;
|
|
138
|
-
}
|
|
139
|
-
await Bun.write(outPath, code);
|
|
140
|
-
return { outputPath: relative(srcDir, srcPath), success: true };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return { success: false };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
-
// Route discovery
|
|
148
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
149
|
-
|
|
150
|
-
async function discoverRoutes(pagesDir) {
|
|
151
|
-
const routes = [];
|
|
152
|
-
|
|
153
|
-
async function scanDirectory(dir, basePath = '') {
|
|
154
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
155
|
-
|
|
156
|
-
for (const entry of entries) {
|
|
157
|
-
const fullPath = join(dir, entry.name);
|
|
158
|
-
const relativePath = join(basePath, entry.name);
|
|
159
|
-
|
|
160
|
-
if (entry.isDirectory()) {
|
|
161
|
-
await scanDirectory(fullPath, relativePath);
|
|
162
|
-
} else if (entry.isFile()) {
|
|
163
|
-
const ext = extname(entry.name);
|
|
164
|
-
if (ext === '.css') continue;
|
|
165
|
-
|
|
166
|
-
if (['.jsx', '.tsx', '.js', '.ts'].includes(ext)) {
|
|
167
|
-
const fileName = entry.name.replace(ext, '');
|
|
168
|
-
if (fileName === 'loading') continue;
|
|
169
|
-
|
|
170
|
-
let route = '/' + relativePath.replace(/\\/g, '/').replace(ext, '');
|
|
171
|
-
if (fileName === 'index') route = route.replace('/index', '') || '/';
|
|
172
|
-
|
|
173
|
-
const isDynamic = fileName.includes('[') && fileName.includes(']');
|
|
174
|
-
routes.push({
|
|
175
|
-
route: route === '' ? '/' : route,
|
|
176
|
-
file: relativePath.replace(/\\/g, '/'),
|
|
177
|
-
path: fullPath,
|
|
178
|
-
type: isDynamic ? 'dynamic' : 'static'
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
await scanDirectory(pagesDir);
|
|
186
|
-
routes.sort((a, b) => {
|
|
187
|
-
if (a.type === b.type) return a.route.localeCompare(b.route);
|
|
188
|
-
return a.type === 'static' ? -1 : 1;
|
|
189
|
-
});
|
|
190
|
-
return routes;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
194
|
-
// Router generation
|
|
195
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
196
|
-
|
|
197
|
-
async function generateRouter(routes, outDir, root) {
|
|
198
|
-
const imports = routes.map((route, i) => {
|
|
199
|
-
const componentName = `Page${i}`;
|
|
200
|
-
const importPath = `./pages/${route.file.replace(/\.(jsx|tsx|ts)$/, '.js')}`;
|
|
201
|
-
return `import ${componentName} from '${importPath}';`;
|
|
202
|
-
}).join('\n');
|
|
203
|
-
|
|
204
|
-
const routeConfigs = routes.map((route, i) =>
|
|
205
|
-
` { path: '${route.route}', component: Page${i}, type: '${route.type}' }`
|
|
206
|
-
).join(',\n');
|
|
207
|
-
|
|
208
|
-
const routerCode = `import React, { useState, useEffect, createContext, useContext } from 'react';
|
|
209
|
-
|
|
210
|
-
const RouterContext = createContext(null);
|
|
211
|
-
|
|
212
|
-
export function useRouter() {
|
|
213
|
-
const context = useContext(RouterContext);
|
|
214
|
-
if (!context) throw new Error('useRouter must be used within a Router');
|
|
215
|
-
return context;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export function Router({ routes }) {
|
|
219
|
-
const [currentRoute, setCurrentRoute] = useState(null);
|
|
220
|
-
const [params, setParams] = useState({});
|
|
221
|
-
|
|
222
|
-
useEffect(() => {
|
|
223
|
-
matchAndSetRoute(window.location.pathname);
|
|
224
|
-
const handlePopState = () => matchAndSetRoute(window.location.pathname);
|
|
225
|
-
window.addEventListener('popstate', handlePopState);
|
|
226
|
-
return () => window.removeEventListener('popstate', handlePopState);
|
|
227
|
-
}, [routes]);
|
|
228
|
-
|
|
229
|
-
function matchAndSetRoute(pathname) {
|
|
230
|
-
for (const route of routes) {
|
|
231
|
-
if (route.type === 'static' && route.path === pathname) {
|
|
232
|
-
setCurrentRoute(route); setParams({}); return;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
for (const route of routes) {
|
|
236
|
-
if (route.type === 'dynamic') {
|
|
237
|
-
const pattern = route.path.replace(/\\[([^\\]]+)\\]/g, '([^/]+)');
|
|
238
|
-
const regex = new RegExp('^' + pattern + '$');
|
|
239
|
-
const match = pathname.match(regex);
|
|
240
|
-
if (match) {
|
|
241
|
-
const paramNames = [...route.path.matchAll(/\\[([^\\]]+)\\]/g)].map(m => m[1]);
|
|
242
|
-
const extracted = {};
|
|
243
|
-
paramNames.forEach((name, i) => { extracted[name] = match[i + 1]; });
|
|
244
|
-
setCurrentRoute(route); setParams(extracted); return;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
setCurrentRoute(null); setParams({});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function navigate(path) {
|
|
252
|
-
window.history.pushState({}, '', path);
|
|
253
|
-
matchAndSetRoute(path);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const Component = currentRoute?.component;
|
|
257
|
-
return React.createElement(
|
|
258
|
-
RouterContext.Provider,
|
|
259
|
-
{ value: { currentRoute, params, navigate, pathname: window.location.pathname } },
|
|
260
|
-
Component ? React.createElement(Component, { params }) : React.createElement(NotFound)
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export function Link({ to, children, ...props }) {
|
|
265
|
-
const { navigate } = useRouter();
|
|
266
|
-
return React.createElement('a', {
|
|
267
|
-
href: to, onClick: (e) => { e.preventDefault(); navigate(to); }, ...props
|
|
268
|
-
}, children);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function NotFound() {
|
|
272
|
-
return React.createElement('div', {
|
|
273
|
-
style: { display: 'flex', flexDirection: 'column', alignItems: 'center',
|
|
274
|
-
justifyContent: 'center', minHeight: '100vh', fontFamily: 'system-ui' }
|
|
275
|
-
},
|
|
276
|
-
React.createElement('h1', { style: { fontSize: '6rem', margin: 0 } }, '404'),
|
|
277
|
-
React.createElement('p', { style: { fontSize: '1.5rem', color: '#666' } }, 'Page not found'),
|
|
278
|
-
React.createElement('a', { href: '/', style: { color: '#10b981', textDecoration: 'none' } }, 'Go home')
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
${imports}
|
|
283
|
-
|
|
284
|
-
export const routes = [
|
|
285
|
-
${routeConfigs}
|
|
286
|
-
];`;
|
|
287
|
-
|
|
288
|
-
await Bun.write(join(outDir, 'router.js'), routerCode);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
292
|
-
// Directory compilation
|
|
293
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
294
|
-
|
|
295
|
-
async function compileDirectory(srcDir, outDir, root, envVars, aliasMap) {
|
|
296
|
-
const stats = { files: 0, skipped: 0 };
|
|
297
|
-
const files = readdirSync(srcDir);
|
|
298
|
-
|
|
299
|
-
for (const file of files) {
|
|
300
|
-
const srcPath = join(srcDir, file);
|
|
301
|
-
const stat = statSync(srcPath);
|
|
302
|
-
|
|
303
|
-
if (stat.isDirectory()) {
|
|
304
|
-
if (file === 'templates') { logger.debug('⏭️ Skipping src/templates/'); continue; }
|
|
305
|
-
if (file === 'api') { logger.debug('⏭️ Skipping src/api/'); continue; }
|
|
306
|
-
const subOutDir = join(outDir, file);
|
|
307
|
-
mkdirSync(subOutDir, { recursive: true });
|
|
308
|
-
const subStats = await compileDirectory(srcPath, subOutDir, root, envVars, aliasMap);
|
|
309
|
-
stats.files += subStats.files;
|
|
310
|
-
stats.skipped += subStats.skipped;
|
|
311
|
-
} else {
|
|
312
|
-
const ext = extname(file);
|
|
313
|
-
const relativePath = relative(join(root, 'src'), srcPath);
|
|
314
|
-
|
|
315
|
-
if (file.endsWith('.module.css')) {
|
|
316
|
-
await compileCSSModule(srcPath, root);
|
|
317
|
-
stats.files++;
|
|
318
|
-
} else if (ext === '.css') {
|
|
319
|
-
const stylesOutDir = join(root, '.bertui', 'styles');
|
|
320
|
-
if (!existsSync(stylesOutDir)) mkdirSync(stylesOutDir, { recursive: true });
|
|
321
|
-
await Bun.write(join(stylesOutDir, file), Bun.file(srcPath));
|
|
322
|
-
stats.files++;
|
|
323
|
-
} else if (['.jsx', '.tsx', '.ts'].includes(ext)) {
|
|
324
|
-
await compileFileInternal(srcPath, outDir, file, relativePath, root, envVars, aliasMap);
|
|
325
|
-
stats.files++;
|
|
326
|
-
} else if (ext === '.js') {
|
|
327
|
-
const outPath = join(outDir, file);
|
|
328
|
-
let code = await Bun.file(srcPath).text();
|
|
329
|
-
code = transformCSSModuleImports(code, srcPath, root);
|
|
330
|
-
code = removePlainCSSImports(code);
|
|
331
|
-
code = replaceEnvInCode(code, envVars);
|
|
332
|
-
code = fixRouterImports(code, outPath, root);
|
|
333
|
-
if (usesJSX(code) && !code.includes('import React')) {
|
|
334
|
-
code = `import React from 'react';\n${code}`;
|
|
335
|
-
}
|
|
336
|
-
// alias rewrite last — after all other transforms
|
|
337
|
-
code = rewriteAliasImports(code, outPath, aliasMap);
|
|
338
|
-
await Bun.write(outPath, code);
|
|
339
|
-
stats.files++;
|
|
340
|
-
} else {
|
|
341
|
-
stats.skipped++;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return stats;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
350
|
-
// CSS Modules
|
|
351
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
352
|
-
|
|
353
|
-
function hashClassName(filename, className) {
|
|
354
|
-
const str = filename + className;
|
|
355
|
-
let hash = 0;
|
|
356
|
-
for (let i = 0; i < str.length; i++) {
|
|
357
|
-
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
358
|
-
hash |= 0;
|
|
359
|
-
}
|
|
360
|
-
return Math.abs(hash).toString(36).slice(0, 5);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function scopeCSSModule(cssText, filename) {
|
|
364
|
-
const classNames = new Set();
|
|
365
|
-
const classRegex = /\.([a-zA-Z_][a-zA-Z0-9_-]*)\s*[{,\s:]/g;
|
|
366
|
-
let match;
|
|
367
|
-
while ((match = classRegex.exec(cssText)) !== null) classNames.add(match[1]);
|
|
368
|
-
|
|
369
|
-
const mapping = {};
|
|
370
|
-
for (const cls of classNames) mapping[cls] = `${cls}_${hashClassName(filename, cls)}`;
|
|
371
|
-
|
|
372
|
-
let scopedCSS = cssText;
|
|
373
|
-
for (const [original, scoped] of Object.entries(mapping)) {
|
|
374
|
-
scopedCSS = scopedCSS.replace(
|
|
375
|
-
new RegExp(`\\.${original}(?=[\\s{,:\\[#.>+~)\\]])`, 'g'),
|
|
376
|
-
`.${scoped}`
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
return { mapping, scopedCSS };
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async function compileCSSModule(srcPath, root) {
|
|
383
|
-
const filename = srcPath.split('/').pop();
|
|
384
|
-
const cssText = await Bun.file(srcPath).text();
|
|
385
|
-
const { mapping, scopedCSS } = scopeCSSModule(cssText, filename);
|
|
386
|
-
|
|
387
|
-
let finalCSS = scopedCSS;
|
|
388
|
-
try {
|
|
389
|
-
const { code } = transform({
|
|
390
|
-
filename,
|
|
391
|
-
code: Buffer.from(scopedCSS),
|
|
392
|
-
minify: false,
|
|
393
|
-
drafts: { nesting: true },
|
|
394
|
-
targets: { chrome: 90 << 16 }
|
|
395
|
-
});
|
|
396
|
-
finalCSS = code.toString();
|
|
397
|
-
} catch (e) {
|
|
398
|
-
logger.warn(`LightningCSS failed for ${filename}: ${e.message}`);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const stylesOutDir = join(root, '.bertui', 'styles');
|
|
402
|
-
if (!existsSync(stylesOutDir)) mkdirSync(stylesOutDir, { recursive: true });
|
|
403
|
-
await Bun.write(join(stylesOutDir, filename), finalCSS);
|
|
404
|
-
|
|
405
|
-
const compiledStylesDir = join(root, '.bertui', 'compiled', 'styles');
|
|
406
|
-
if (!existsSync(compiledStylesDir)) mkdirSync(compiledStylesDir, { recursive: true });
|
|
407
|
-
const jsContent = `// CSS Module: ${filename}\nconst styles = ${JSON.stringify(mapping, null, 2)};\nexport default styles;\n`;
|
|
408
|
-
await Bun.write(join(compiledStylesDir, filename + '.js'), jsContent);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function transformCSSModuleImports(code, srcPath, root) {
|
|
412
|
-
const moduleImportRegex = /import\s+(\w+)\s+from\s+['"]([^'"]*\.module\.css)['"]/g;
|
|
413
|
-
const srcDir = join(root, 'src');
|
|
414
|
-
const relativeFromSrc = relative(srcDir, srcPath);
|
|
415
|
-
const compiledFilePath = join(root, '.bertui', 'compiled', relativeFromSrc.replace(/\.(jsx|tsx|ts)$/, '.js'));
|
|
416
|
-
const compiledFileDir = dirname(compiledFilePath);
|
|
417
|
-
const compiledStylesDir = join(root, '.bertui', 'compiled', 'styles');
|
|
418
|
-
|
|
419
|
-
code = code.replace(moduleImportRegex, (match, varName, importPath) => {
|
|
420
|
-
const filename = importPath.split('/').pop();
|
|
421
|
-
const jsFile = join(compiledStylesDir, filename + '.js');
|
|
422
|
-
let rel = relative(compiledFileDir, jsFile).replace(/\\/g, '/');
|
|
423
|
-
if (!rel.startsWith('.')) rel = './' + rel;
|
|
424
|
-
return `import ${varName} from '${rel}'`;
|
|
425
|
-
});
|
|
426
|
-
return code;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function removePlainCSSImports(code) {
|
|
430
|
-
code = code.replace(/import\s+['"][^'"]*(?<!\.module)\.css['"];?\s*/g, '');
|
|
431
|
-
code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
|
|
432
|
-
return code;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
436
|
-
// File compilation
|
|
437
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
438
|
-
|
|
439
|
-
async function compileFileInternal(srcPath, outDir, filename, relativePath, root, envVars, aliasMap) {
|
|
440
|
-
const ext = extname(filename);
|
|
441
|
-
const loader = ext === '.tsx' ? 'tsx' : ext === '.ts' ? 'ts' : 'jsx';
|
|
442
|
-
|
|
443
|
-
try {
|
|
444
|
-
let code = await Bun.file(srcPath).text();
|
|
445
|
-
code = transformCSSModuleImports(code, srcPath, root);
|
|
446
|
-
code = removePlainCSSImports(code);
|
|
447
|
-
code = removeDotenvImports(code);
|
|
448
|
-
code = replaceEnvInCode(code, envVars);
|
|
449
|
-
|
|
450
|
-
const outPath = join(outDir, filename.replace(/\.(jsx|tsx|ts)$/, '.js'));
|
|
451
|
-
code = fixRouterImports(code, outPath, root);
|
|
452
|
-
|
|
453
|
-
const transpiler = new Bun.Transpiler({
|
|
454
|
-
loader,
|
|
455
|
-
tsconfig: {
|
|
456
|
-
compilerOptions: {
|
|
457
|
-
jsx: 'react',
|
|
458
|
-
jsxFactory: 'React.createElement',
|
|
459
|
-
jsxFragmentFactory: 'React.Fragment'
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
let compiled = await transpiler.transform(code);
|
|
464
|
-
|
|
465
|
-
if (usesJSX(compiled) && !compiled.includes('import React')) {
|
|
466
|
-
compiled = `import React from 'react';\n${compiled}`;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
compiled = fixRelativeImports(compiled);
|
|
470
|
-
// ← alias rewrite MUST happen after transpiler — Bun normalizes specifiers during transform
|
|
471
|
-
compiled = rewriteAliasImports(compiled, outPath, aliasMap);
|
|
472
|
-
await Bun.write(outPath, compiled);
|
|
473
|
-
} catch (error) {
|
|
474
|
-
// Enrich error with file info so the watcher can forward it to the overlay
|
|
475
|
-
error.file = relativePath;
|
|
476
|
-
const detail = error.errors?.[0];
|
|
477
|
-
if (detail) {
|
|
478
|
-
error.message = detail.text || error.message;
|
|
479
|
-
error.line = detail.position?.line;
|
|
480
|
-
error.column = detail.position?.column;
|
|
481
|
-
}
|
|
482
|
-
logger.error(`Failed to compile ${relativePath}: ${error.message}`);
|
|
483
|
-
throw error;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
488
|
-
// Helpers
|
|
489
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
490
|
-
|
|
491
|
-
function usesJSX(code) {
|
|
492
|
-
return code.includes('React.createElement') ||
|
|
493
|
-
code.includes('React.Fragment') ||
|
|
494
|
-
/<[A-Z]/.test(code) ||
|
|
495
|
-
code.includes('jsx(') ||
|
|
496
|
-
code.includes('jsxs(');
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function removeDotenvImports(code) {
|
|
500
|
-
code = code.replace(/import\s+\w+\s+from\s+['"]dotenv['"]\s*;?\s*/g, '');
|
|
501
|
-
code = code.replace(/import\s+\{[^}]+\}\s+from\s+['"]dotenv['"]\s*;?\s*/g, '');
|
|
502
|
-
code = code.replace(/\w+\.config\(\s*\)\s*;?\s*/g, '');
|
|
503
|
-
return code;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function fixRouterImports(code, outPath, root) {
|
|
507
|
-
const buildDir = join(root, '.bertui', 'compiled');
|
|
508
|
-
const routerPath = join(buildDir, 'router.js');
|
|
509
|
-
const rel = relative(dirname(outPath), routerPath).replace(/\\/g, '/');
|
|
510
|
-
const routerImport = rel.startsWith('.') ? rel : './' + rel;
|
|
511
|
-
code = code.replace(/from\s+['"]bertui\/router['"]/g, `from '${routerImport}'`);
|
|
512
|
-
return code;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function fixRelativeImports(code) {
|
|
516
|
-
const importRegex = /from\s+['"](\.\.?\/[^'"]+?)(?<!\.js|\.jsx|\.ts|\.tsx|\.json)['"]/g;
|
|
517
|
-
code = code.replace(importRegex, (match, path) => {
|
|
518
|
-
if (path.endsWith('/') || /\.\w+$/.test(path)) return match;
|
|
519
|
-
return `from '${path}.js'`;
|
|
520
|
-
});
|
|
521
|
-
return code;
|
|
522
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// bertui/src/client/fast-refresh.js
|
|
2
|
-
// React Fast Refresh integration
|
|
3
|
-
|
|
4
|
-
import RefreshRuntime from 'react-refresh';
|
|
5
|
-
|
|
6
|
-
// Inject into global scope
|
|
7
|
-
if (typeof window !== 'undefined') {
|
|
8
|
-
// Setup Fast Refresh globals
|
|
9
|
-
window.$RefreshReg$ = (type, id) => {
|
|
10
|
-
RefreshRuntime.register(type, id);
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
|
|
14
|
-
|
|
15
|
-
// Store runtime in global
|
|
16
|
-
window.$RefreshRuntime$ = RefreshRuntime;
|
|
17
|
-
|
|
18
|
-
// Inject into global hook
|
|
19
|
-
if (!window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
|
|
20
|
-
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
|
21
|
-
supportsFiber: true,
|
|
22
|
-
inject: (fiber) => {},
|
|
23
|
-
onCommitFiberRoot: (rendererId, root) => {},
|
|
24
|
-
onCommitFiberUnmount: () => {}
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Setup refresh handler
|
|
29
|
-
RefreshRuntime.injectIntoGlobalHook(window);
|
|
30
|
-
|
|
31
|
-
// Create queue for batched updates
|
|
32
|
-
let updateQueue = [];
|
|
33
|
-
let scheduled = false;
|
|
34
|
-
|
|
35
|
-
const scheduleUpdate = () => {
|
|
36
|
-
if (scheduled) return;
|
|
37
|
-
scheduled = true;
|
|
38
|
-
|
|
39
|
-
queueMicrotask(() => {
|
|
40
|
-
scheduled = false;
|
|
41
|
-
const queue = updateQueue;
|
|
42
|
-
updateQueue = [];
|
|
43
|
-
|
|
44
|
-
if (queue.length > 0 && window.$RefreshRuntime$) {
|
|
45
|
-
window.$RefreshRuntime$.performReactRefresh();
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Listen for HMR updates
|
|
51
|
-
window.addEventListener('hmr-module-updated', (event) => {
|
|
52
|
-
const { moduleId } = event.detail;
|
|
53
|
-
updateQueue.push(moduleId);
|
|
54
|
-
scheduleUpdate();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
console.log('%c⚡ React Fast Refresh enabled', 'color: #3b82f6; font-weight: bold');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Export for module usage
|
|
61
|
-
export const setupFastRefresh = () => {
|
|
62
|
-
if (typeof window !== 'undefined' && window.$RefreshRuntime$) {
|
|
63
|
-
return window.$RefreshRuntime$;
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export const performReactRefresh = () => {
|
|
69
|
-
if (typeof window !== 'undefined' && window.$RefreshRuntime$) {
|
|
70
|
-
window.$RefreshRuntime$.performReactRefresh();
|
|
71
|
-
}
|
|
72
|
-
};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// bertui/src/client/hmr-runtime.js
|
|
2
|
-
// ONE SOLUTION - Fast Refresh HMR
|
|
3
|
-
|
|
4
|
-
(function(global) {
|
|
5
|
-
let socket = null;
|
|
6
|
-
let reconnectTimer = null;
|
|
7
|
-
|
|
8
|
-
function connect() {
|
|
9
|
-
const protocol = global.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
10
|
-
socket = new WebSocket(`${protocol}//${global.location.host}/__hmr`);
|
|
11
|
-
|
|
12
|
-
socket.onopen = () => {
|
|
13
|
-
console.log('%c🔥 HMR connected', 'color: #10b981; font-weight: bold');
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
socket.onmessage = async (event) => {
|
|
17
|
-
const data = JSON.parse(event.data);
|
|
18
|
-
|
|
19
|
-
if (data.type === 'hmr-update') {
|
|
20
|
-
console.log(`%c🔥 HMR: ${data.module} (${data.time}ms)`, 'color: #10b981');
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const url = new URL(data.module, global.location.origin);
|
|
24
|
-
url.searchParams.set('t', Date.now());
|
|
25
|
-
await import(url.toString());
|
|
26
|
-
|
|
27
|
-
if (global.$RefreshRuntime$) {
|
|
28
|
-
global.$RefreshRuntime$.performReactRefresh();
|
|
29
|
-
}
|
|
30
|
-
} catch (err) {
|
|
31
|
-
console.error('HMR update failed:', err);
|
|
32
|
-
global.location.reload();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (data.type === 'full-reload') {
|
|
37
|
-
global.location.reload();
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
socket.onclose = () => {
|
|
42
|
-
console.log('%c⚠️ HMR disconnected, reconnecting...', 'color: #f59e0b');
|
|
43
|
-
reconnectTimer = setTimeout(connect, 2000);
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (global.document) {
|
|
48
|
-
global.addEventListener('load', connect);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Fast Refresh setup
|
|
52
|
-
if (typeof window !== 'undefined') {
|
|
53
|
-
window.$RefreshReg$ = (type, id) => {};
|
|
54
|
-
window.$RefreshSig$ = () => (type) => type;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
})(typeof window !== 'undefined' ? window : global);
|
|
58
|
-
|
|
59
|
-
export const hmr = { connect: () => {} };
|
package/src/compiler/index.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// bertui/src/compiler/index.js - NEW FILE (PURE, NO SERVER)
|
|
2
|
-
export { compileProject } from '../client/compiler.js';
|
|
3
|
-
export { compileForBuild } from '../build/compiler/index.js';
|
|
4
|
-
export { discoverRoutes } from '../build/compiler/route-discoverer.js';
|
|
5
|
-
export { validateServerIsland } from '../build/server-island-validator.js';
|
|
6
|
-
|
|
7
|
-
// PURE JSX→JS TRANSFORMATION
|
|
8
|
-
export async function transformJSX(sourceCode, options = {}) {
|
|
9
|
-
const transpiler = new Bun.Transpiler({
|
|
10
|
-
loader: options.loader || 'tsx',
|
|
11
|
-
target: 'browser',
|
|
12
|
-
define: {
|
|
13
|
-
'process.env.NODE_ENV': JSON.stringify(options.env || 'development')
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
return await transpiler.transform(sourceCode);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Export everything from this directory
|
|
20
|
-
export { transformJSX, transformJSXSync, containsJSX, removeCSSImports, removeDotenvImports, fixRelativeImports } from './transform.js';
|
|
21
|
-
export { generateRouterCode } from './router-generator-pure.js';
|
|
22
|
-
// Re-export existing
|
|
23
|
-
export { compileProject } from '../client/compiler.js';
|
|
24
|
-
export { compileForBuild } from '../build/compiler/index.js';
|
|
25
|
-
export { discoverRoutes } from '../build/compiler/route-discoverer.js';
|