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.
Files changed (182) hide show
  1. package/README.md +45 -196
  2. package/TYPES_PATCH.md +17 -0
  3. package/bin/bertui.js +2 -7
  4. package/package.json +32 -98
  5. package/src/config.ts +4 -0
  6. package/src/index.ts +32 -0
  7. package/src/optional.ts +49 -0
  8. package/src/router.ts +3 -0
  9. package/tsconfig.json +29 -0
  10. package/LICENSE +0 -21
  11. package/index.js +0 -103
  12. package/src/analyzer/index.js +0 -370
  13. package/src/build/compiler/file-transpiler.js +0 -216
  14. package/src/build/compiler/index.js +0 -31
  15. package/src/build/compiler/route-discoverer.js +0 -49
  16. package/src/build/compiler/router-generator.js +0 -105
  17. package/src/build/css-builder.js +0 -81
  18. package/src/build/generators/html-generator.js +0 -151
  19. package/src/build/generators/robots-generator.js +0 -58
  20. package/src/build/generators/sitemap-generator.js +0 -63
  21. package/src/build/image-optimizer.js +0 -137
  22. package/src/build/processors/asset-processor.js +0 -19
  23. package/src/build/processors/css-builder.js +0 -142
  24. package/src/build/server-island-validator.js +0 -12
  25. package/src/build.js +0 -266
  26. package/src/cli.js +0 -131
  27. package/src/client/compiler.js +0 -522
  28. package/src/client/fast-refresh.js +0 -72
  29. package/src/client/hmr-runtime.js +0 -59
  30. package/src/compiler/index.js +0 -25
  31. package/src/compiler/router-generator-pure.js +0 -104
  32. package/src/compiler/transform.js +0 -149
  33. package/src/config/defaultConfig.js +0 -37
  34. package/src/config/index.js +0 -2
  35. package/src/config/loadConfig.js +0 -64
  36. package/src/config/og-image.png +0 -0
  37. package/src/css/index.js +0 -46
  38. package/src/css/processor.js +0 -172
  39. package/src/dev.js +0 -68
  40. package/src/hydration/index.js +0 -151
  41. package/src/image-optimizer/index.js +0 -103
  42. package/src/images/index.js +0 -102
  43. package/src/images/processor.js +0 -169
  44. package/src/layouts/index.js +0 -165
  45. package/src/loading/index.js +0 -210
  46. package/src/logger/logger.js +0 -320
  47. package/src/logger/notes.md +0 -20
  48. package/src/middleware/index.js +0 -182
  49. package/src/router/Router.js +0 -150
  50. package/src/router/SSRRouter.js +0 -156
  51. package/src/router/index.js +0 -3
  52. package/src/scaffolder/index.js +0 -310
  53. package/src/serve.js +0 -193
  54. package/src/server/dev-handler.js +0 -195
  55. package/src/server/dev-server-utils.js +0 -406
  56. package/src/server/dev-server.js +0 -15
  57. package/src/server/hmr-handler.js +0 -148
  58. package/src/server/index.js +0 -3
  59. package/src/server/notes.md +0 -1
  60. package/src/server/request-handler.js +0 -36
  61. package/src/server-islands/extractor.js +0 -198
  62. package/src/server-islands/index.js +0 -59
  63. package/src/styles/bertui.css +0 -210
  64. package/src/utils/cache.js +0 -297
  65. package/src/utils/env.js +0 -87
  66. package/src/utils/importhow.js +0 -52
  67. package/src/utils/index.js +0 -11
  68. package/src/utils/meta-extractor.js +0 -127
  69. package/types/bin/bertui.d.ts +0 -3
  70. package/types/bin/bertui.d.ts.map +0 -1
  71. package/types/error-overlay.d.ts +0 -2
  72. package/types/error-overlay.d.ts.map +0 -1
  73. package/types/index.d.ts +0 -26
  74. package/types/index.d.ts.map +0 -1
  75. package/types/scripts/fix-wasm-exports.d.ts +0 -2
  76. package/types/scripts/fix-wasm-exports.d.ts.map +0 -1
  77. package/types/src/analyzer/index.d.ts +0 -8
  78. package/types/src/analyzer/index.d.ts.map +0 -1
  79. package/types/src/build/compiler/file-transpiler.d.ts +0 -5
  80. package/types/src/build/compiler/file-transpiler.d.ts.map +0 -1
  81. package/types/src/build/compiler/index.d.ts +0 -12
  82. package/types/src/build/compiler/index.d.ts.map +0 -1
  83. package/types/src/build/compiler/route-discoverer.d.ts +0 -2
  84. package/types/src/build/compiler/route-discoverer.d.ts.map +0 -1
  85. package/types/src/build/compiler/router-generator.d.ts +0 -2
  86. package/types/src/build/compiler/router-generator.d.ts.map +0 -1
  87. package/types/src/build/css-builder.d.ts +0 -18
  88. package/types/src/build/css-builder.d.ts.map +0 -1
  89. package/types/src/build/generators/html-generator.d.ts +0 -2
  90. package/types/src/build/generators/html-generator.d.ts.map +0 -1
  91. package/types/src/build/generators/robots-generator.d.ts +0 -11
  92. package/types/src/build/generators/robots-generator.d.ts.map +0 -1
  93. package/types/src/build/generators/sitemap-generator.d.ts +0 -5
  94. package/types/src/build/generators/sitemap-generator.d.ts.map +0 -1
  95. package/types/src/build/image-optimizer.d.ts +0 -11
  96. package/types/src/build/image-optimizer.d.ts.map +0 -1
  97. package/types/src/build/processors/asset-processor.d.ts +0 -2
  98. package/types/src/build/processors/asset-processor.d.ts.map +0 -1
  99. package/types/src/build/processors/css-builder.d.ts +0 -2
  100. package/types/src/build/processors/css-builder.d.ts.map +0 -1
  101. package/types/src/build/server-island-validator.d.ts +0 -27
  102. package/types/src/build/server-island-validator.d.ts.map +0 -1
  103. package/types/src/build.d.ts +0 -5
  104. package/types/src/build.d.ts.map +0 -1
  105. package/types/src/cli.d.ts +0 -2
  106. package/types/src/cli.d.ts.map +0 -1
  107. package/types/src/client/compiler.d.ts +0 -16
  108. package/types/src/client/compiler.d.ts.map +0 -1
  109. package/types/src/client/fast-refresh.d.ts +0 -3
  110. package/types/src/client/fast-refresh.d.ts.map +0 -1
  111. package/types/src/client/hmr-runtime.d.ts +0 -4
  112. package/types/src/client/hmr-runtime.d.ts.map +0 -1
  113. package/types/src/compiler/index.d.ts +0 -8
  114. package/types/src/compiler/index.d.ts.map +0 -1
  115. package/types/src/compiler/router-generator-pure.d.ts +0 -2
  116. package/types/src/compiler/router-generator-pure.d.ts.map +0 -1
  117. package/types/src/compiler/transform.d.ts +0 -36
  118. package/types/src/compiler/transform.d.ts.map +0 -1
  119. package/types/src/config/defaultConfig.d.ts +0 -26
  120. package/types/src/config/defaultConfig.d.ts.map +0 -1
  121. package/types/src/config/index.d.ts +0 -3
  122. package/types/src/config/index.d.ts.map +0 -1
  123. package/types/src/config/loadConfig.d.ts +0 -2
  124. package/types/src/config/loadConfig.d.ts.map +0 -1
  125. package/types/src/css/index.d.ts +0 -6
  126. package/types/src/css/index.d.ts.map +0 -1
  127. package/types/src/css/processor.d.ts +0 -23
  128. package/types/src/css/processor.d.ts.map +0 -1
  129. package/types/src/dev.d.ts +0 -2
  130. package/types/src/dev.d.ts.map +0 -1
  131. package/types/src/hydration/index.d.ts +0 -33
  132. package/types/src/hydration/index.d.ts.map +0 -1
  133. package/types/src/image-optimizer/index.d.ts +0 -24
  134. package/types/src/image-optimizer/index.d.ts.map +0 -1
  135. package/types/src/images/index.d.ts +0 -12
  136. package/types/src/images/index.d.ts.map +0 -1
  137. package/types/src/images/processor.d.ts +0 -30
  138. package/types/src/images/processor.d.ts.map +0 -1
  139. package/types/src/layouts/index.d.ts +0 -28
  140. package/types/src/layouts/index.d.ts.map +0 -1
  141. package/types/src/loading/index.d.ts +0 -28
  142. package/types/src/loading/index.d.ts.map +0 -1
  143. package/types/src/logger/logger.d.ts +0 -30
  144. package/types/src/logger/logger.d.ts.map +0 -1
  145. package/types/src/middleware/index.d.ts +0 -61
  146. package/types/src/middleware/index.d.ts.map +0 -1
  147. package/types/src/router/Router.d.ts +0 -16
  148. package/types/src/router/Router.d.ts.map +0 -1
  149. package/types/src/router/SSRRouter.d.ts +0 -20
  150. package/types/src/router/SSRRouter.d.ts.map +0 -1
  151. package/types/src/router/index.d.ts +0 -3
  152. package/types/src/router/index.d.ts.map +0 -1
  153. package/types/src/scaffolder/index.d.ts +0 -14
  154. package/types/src/scaffolder/index.d.ts.map +0 -1
  155. package/types/src/serve.d.ts +0 -3
  156. package/types/src/serve.d.ts.map +0 -1
  157. package/types/src/server/dev-handler.d.ts +0 -13
  158. package/types/src/server/dev-handler.d.ts.map +0 -1
  159. package/types/src/server/dev-server-utils.d.ts +0 -6
  160. package/types/src/server/dev-server-utils.d.ts.map +0 -1
  161. package/types/src/server/dev-server.d.ts +0 -18
  162. package/types/src/server/dev-server.d.ts.map +0 -1
  163. package/types/src/server/hmr-handler.d.ts +0 -19
  164. package/types/src/server/hmr-handler.d.ts.map +0 -1
  165. package/types/src/server/index.d.ts +0 -4
  166. package/types/src/server/index.d.ts.map +0 -1
  167. package/types/src/server/request-handler.d.ts +0 -19
  168. package/types/src/server/request-handler.d.ts.map +0 -1
  169. package/types/src/server-islands/extractor.d.ts +0 -16
  170. package/types/src/server-islands/extractor.d.ts.map +0 -1
  171. package/types/src/server-islands/index.d.ts +0 -3
  172. package/types/src/server-islands/index.d.ts.map +0 -1
  173. package/types/src/utils/cache.d.ts +0 -52
  174. package/types/src/utils/cache.d.ts.map +0 -1
  175. package/types/src/utils/env.d.ts +0 -20
  176. package/types/src/utils/env.d.ts.map +0 -1
  177. package/types/src/utils/importhow.d.ts +0 -15
  178. package/types/src/utils/importhow.d.ts.map +0 -1
  179. package/types/src/utils/index.d.ts +0 -3
  180. package/types/src/utils/index.d.ts.map +0 -1
  181. package/types/src/utils/meta-extractor.d.ts +0 -13
  182. package/types/src/utils/meta-extractor.d.ts.map +0 -1
@@ -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: () => {} };
@@ -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';