bertui 1.2.1 β†’ 1.2.3

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/src/build.js CHANGED
@@ -1,6 +1,6 @@
1
- // bertui/src/build.js - WITH LAYOUTS + LOADING + PARTIAL HYDRATION + ANALYZER
1
+ // bertui/src/build.js
2
2
  import { join } from 'path';
3
- import { existsSync, mkdirSync, rmSync } from 'fs';
3
+ import { existsSync, mkdirSync, rmSync, readdirSync, statSync } from 'fs';
4
4
  import logger from './logger/logger.js';
5
5
  import { loadEnvVariables } from './utils/env.js';
6
6
  import { globalCache } from './utils/cache.js';
@@ -15,135 +15,194 @@ import { compileLayouts } from './layouts/index.js';
15
15
  import { compileLoadingComponents } from './loading/index.js';
16
16
  import { analyzeRoutes, logHydrationReport } from './hydration/index.js';
17
17
  import { analyzeBuild } from './analyzer/index.js';
18
+ import { buildAliasMap } from './utils/importhow.js';
19
+
20
+ const TOTAL_STEPS = 10;
18
21
 
19
22
  export async function buildProduction(options = {}) {
20
- const root = options.root || process.cwd();
23
+ const root = options.root || process.cwd();
21
24
  const buildDir = join(root, '.bertuibuild');
22
- const outDir = join(root, 'dist');
25
+ const outDir = join(root, 'dist');
23
26
 
24
27
  process.env.NODE_ENV = 'production';
25
28
 
26
- logger.bigLog('BUILDING WITH SERVER ISLANDS 🏝️', { color: 'green' });
29
+ logger.printHeader('BUILD');
27
30
 
28
31
  if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
29
- if (existsSync(outDir)) rmSync(outDir, { recursive: true, force: true });
32
+ if (existsSync(outDir)) rmSync(outDir, { recursive: true, force: true });
30
33
  mkdirSync(buildDir, { recursive: true });
31
- mkdirSync(outDir, { recursive: true });
34
+ mkdirSync(outDir, { recursive: true });
32
35
 
33
- const startTime = process.hrtime.bigint();
36
+ let totalKB = '0';
34
37
 
35
38
  try {
36
- logger.info('Step 0: Loading environment variables...');
39
+ // ── Step 1: Env ──────────────────────────────────────────────────────────
40
+ logger.step(1, TOTAL_STEPS, 'Loading env');
37
41
  const envVars = loadEnvVariables(root);
38
-
39
42
  const { loadConfig } = await import('./config/loadConfig.js');
40
- const config = await loadConfig(root);
41
-
42
- logger.info('Step 1: Compiling project...');
43
- const { routes, serverIslands, clientRoutes } = await compileForBuild(root, buildDir, envVars);
44
-
45
- if (serverIslands.length > 0) {
46
- logger.bigLog('SERVER ISLANDS DETECTED 🏝️', { color: 'cyan' });
47
- logger.table(serverIslands.map(r => ({
48
- route: r.route,
49
- file: r.file,
50
- mode: '🏝️ Server Island (SSG)',
51
- })));
52
- }
43
+ const config = await loadConfig(root);
44
+ const importhow = config.importhow || {};
45
+ logger.stepDone('Loading env', `${Object.keys(envVars).length} vars`);
46
+
47
+ // ── Step 2: Compile ──────────────────────────────────────────────────────
48
+ logger.step(2, TOTAL_STEPS, 'Compiling');
49
+ const { routes, serverIslands, clientRoutes } = await compileForBuild(root, buildDir, envVars, config);
50
+ logger.stepDone('Compiling', `${routes.length} routes Β· ${serverIslands.length} islands`);
53
51
 
54
- // βœ… NEW: Compile layouts
55
- logger.info('Step 2: Compiling layouts...');
52
+ // ── Step 3: Layouts ──────────────────────────────────────────────────────
53
+ logger.step(3, TOTAL_STEPS, 'Layouts');
56
54
  const layouts = await compileLayouts(root, buildDir);
57
- const layoutCount = Object.keys(layouts).length;
58
- if (layoutCount > 0) {
59
- logger.success(`πŸ“ ${layoutCount} layout(s) compiled`);
60
- }
55
+ logger.stepDone('Layouts', `${Object.keys(layouts).length} found`);
61
56
 
62
- // βœ… NEW: Compile per-route loading states
63
- logger.info('Step 3: Compiling loading states...');
64
- const loadingComponents = await compileLoadingComponents(root, buildDir);
57
+ // ── Step 4: Loading states ───────────────────────────────────────────────
58
+ logger.step(4, TOTAL_STEPS, 'Loading states');
59
+ await compileLoadingComponents(root, buildDir);
60
+ logger.stepDone('Loading states');
65
61
 
66
- // βœ… NEW: Partial hydration analysis
67
- logger.info('Step 4: Analyzing routes for partial hydration...');
62
+ // ── Step 5: Hydration analysis ───────────────────────────────────────────
63
+ logger.step(5, TOTAL_STEPS, 'Hydration analysis');
68
64
  const analyzedRoutes = await analyzeRoutes(routes);
69
- logHydrationReport(analyzedRoutes);
65
+ logger.stepDone('Hydration analysis',
66
+ `${analyzedRoutes.interactive.length} interactive Β· ${analyzedRoutes.static.length} static`);
70
67
 
71
- logger.info('Step 5: Processing CSS...');
68
+ // ── Step 6: CSS ──────────────────────────────────────────────────────────
69
+ logger.step(6, TOTAL_STEPS, 'Processing CSS');
72
70
  await buildAllCSS(root, outDir);
71
+ logger.stepDone('Processing CSS');
73
72
 
74
- logger.info('Step 6: Copying static assets...');
73
+ // ── Step 7: Static assets ────────────────────────────────────────────────
74
+ logger.step(7, TOTAL_STEPS, 'Static assets');
75
75
  await copyAllStaticAssets(root, outDir);
76
+ logger.stepDone('Static assets');
76
77
 
77
- logger.info('Step 7: Bundling JavaScript...');
78
+ // ── Step 8: Bundle JS ────────────────────────────────────────────────────
79
+ logger.step(8, TOTAL_STEPS, 'Bundling JS');
78
80
  const buildEntry = join(buildDir, 'main.js');
79
81
  const routerPath = join(buildDir, 'router.js');
82
+ if (!existsSync(buildEntry)) throw new Error('main.js not found in build dir');
83
+ const result = await bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir, analyzedRoutes, importhow, root, config);
84
+ totalKB = (result.outputs.reduce((a, o) => a + (o.size || 0), 0) / 1024).toFixed(1);
85
+ logger.stepDone('Bundling JS', `${totalKB} KB Β· tree-shaken`);
80
86
 
81
- if (!existsSync(buildEntry)) {
82
- throw new Error('Build entry point missing (main.js not found in build dir)');
83
- }
84
-
85
- const result = await bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir, analyzedRoutes);
86
-
87
- logger.info('Step 8: Generating HTML...');
87
+ // ── Step 9: HTML ─────────────────────────────────────────────────────────
88
+ logger.step(9, TOTAL_STEPS, 'Generating HTML');
88
89
  await generateProductionHTML(root, outDir, result, routes, serverIslands, config);
90
+ logger.stepDone('Generating HTML', `${routes.length} pages`);
89
91
 
90
- logger.info('Step 9: Generating sitemap.xml...');
92
+ // ── Step 10: Sitemap + robots ────────────────────────────────────────────
93
+ logger.step(10, TOTAL_STEPS, 'Sitemap & robots');
91
94
  await generateSitemap(routes, config, outDir);
92
-
93
- logger.info('Step 10: Generating robots.txt...');
94
95
  await generateRobots(config, outDir, routes);
96
+ logger.stepDone('Sitemap & robots');
95
97
 
96
- // Cleanup build directory
97
98
  if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
98
99
 
99
- const endTime = process.hrtime.bigint();
100
- const durationMs = Number(endTime - startTime) / 1_000_000;
101
-
102
- showBuildSummary(routes, serverIslands, clientRoutes, analyzedRoutes, durationMs);
103
-
104
- // βœ… NEW: Auto-generate bundle report
105
- logger.info('Generating bundle report...');
106
- await analyzeBuild(outDir, {
107
- outputFile: join(outDir, 'bundle-report.html'),
100
+ // Fire-and-forget β€” don't let the report generator block process exit
101
+ analyzeBuild(outDir, { outputFile: join(outDir, 'bundle-report.html') }).catch(() => {});
102
+
103
+ // ── Summary ──────────────────────────────────────────────────────────────
104
+ logger.printSummary({
105
+ routes: routes.length,
106
+ serverIslands: serverIslands.length,
107
+ interactive: analyzedRoutes.interactive.length,
108
+ staticRoutes: analyzedRoutes.static.length,
109
+ jsSize: `${totalKB} KB`,
110
+ outDir: 'dist/',
108
111
  });
109
112
 
110
- setTimeout(() => {
111
- logger.info('βœ… Build complete');
112
- process.exit(0);
113
- }, 100);
113
+ return { success: true };
114
114
 
115
115
  } catch (error) {
116
- logger.error(`Build failed: ${error.message}`);
117
- if (error.stack) logger.error(error.stack);
116
+ logger.stepFail('Build', error.message);
118
117
  if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ // ─────────────────────────────────────────────────────────────────────────────
119
123
 
120
- setTimeout(() => process.exit(1), 100);
124
+ async function generateProductionImportMap(root, config) {
125
+ const importMap = {
126
+ 'react': 'https://esm.sh/react@18.2.0',
127
+ 'react-dom': 'https://esm.sh/react-dom@18.2.0',
128
+ 'react-dom/client': 'https://esm.sh/react-dom@18.2.0/client',
129
+ 'react/jsx-runtime': 'https://esm.sh/react@18.2.0/jsx-runtime',
130
+ };
131
+
132
+ const nodeModulesDir = join(root, 'node_modules');
133
+ if (!existsSync(nodeModulesDir)) return importMap;
134
+
135
+ try {
136
+ for (const pkg of readdirSync(nodeModulesDir)) {
137
+ if (!pkg.startsWith('bertui-') || pkg.startsWith('.')) continue;
138
+ const pkgDir = join(nodeModulesDir, pkg);
139
+ const pkgJson = join(pkgDir, 'package.json');
140
+ if (!existsSync(pkgJson)) continue;
141
+ try {
142
+ const p = JSON.parse(await Bun.file(pkgJson).text());
143
+ for (const entry of [p.browser, p.module, p.main, 'dist/index.js', 'index.js'].filter(Boolean)) {
144
+ if (existsSync(join(pkgDir, entry))) {
145
+ importMap[pkg] = `/assets/node_modules/${pkg}/${entry}`;
146
+ break;
147
+ }
148
+ }
149
+ } catch { continue; }
150
+ }
151
+ } catch { /* ignore */ }
152
+
153
+ return importMap;
154
+ }
155
+
156
+ async function copyNodeModulesToDist(root, outDir, importMap) {
157
+ const dest = join(outDir, 'assets', 'node_modules');
158
+ mkdirSync(dest, { recursive: true });
159
+ const src = join(root, 'node_modules');
160
+
161
+ for (const [, assetPath] of Object.entries(importMap)) {
162
+ if (assetPath.startsWith('https://')) continue;
163
+ const match = assetPath.match(/\/assets\/node_modules\/(.+)$/);
164
+ if (!match) continue;
165
+ const parts = match[1].split('/');
166
+ const pkgName = parts[0];
167
+ const subPath = parts.slice(1);
168
+ const srcFile = join(src, pkgName, ...subPath);
169
+ const destFile = join(dest, pkgName, ...subPath);
170
+ mkdirSync(join(dest, pkgName, ...subPath.slice(0, -1)), { recursive: true });
171
+ if (existsSync(srcFile)) await Bun.write(destFile, Bun.file(srcFile));
121
172
  }
122
173
  }
123
174
 
124
- async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir, analyzedRoutes) {
175
+ async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir, analyzedRoutes, importhow, root, config) {
125
176
  const originalCwd = process.cwd();
126
177
  process.chdir(buildDir);
127
178
 
128
179
  try {
129
- const hasRouter = existsSync(routerPath);
130
180
  const entrypoints = [buildEntry];
131
- if (hasRouter) entrypoints.push(routerPath);
181
+ if (existsSync(routerPath)) entrypoints.push(routerPath);
182
+
183
+ const importMap = await generateProductionImportMap(root, config);
184
+ await Bun.write(join(outDir, 'import-map.json'), JSON.stringify({ imports: importMap }, null, 2));
185
+ await copyNodeModulesToDist(root, outDir, importMap);
132
186
 
133
187
  const result = await Bun.build({
134
188
  entrypoints,
135
- outdir: join(outDir, 'assets'),
136
- target: 'browser',
137
- minify: true,
138
- splitting: true, // Code splitting per route
189
+ outdir: join(outDir, 'assets'),
190
+ target: 'browser',
191
+ format: 'esm',
192
+ minify: {
193
+ whitespace: true,
194
+ syntax: true,
195
+ identifiers: true,
196
+ },
197
+ splitting: true,
139
198
  sourcemap: 'external',
140
- metafile: true, // βœ… Enable for analyzer
199
+ metafile: true,
141
200
  naming: {
142
- entry: '[name]-[hash].js',
143
- chunk: 'chunks/[name]-[hash].js',
144
- asset: '[name]-[hash].[ext]',
201
+ entry: 'js/[name]-[hash].js',
202
+ chunk: 'js/chunks/[name]-[hash].js',
203
+ asset: 'assets/[name]-[hash].[ext]',
145
204
  },
146
- external: ['react', 'react-dom', 'react-dom/client'],
205
+ external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime'],
147
206
  define: {
148
207
  'process.env.NODE_ENV': '"production"',
149
208
  ...Object.fromEntries(
@@ -153,13 +212,13 @@ async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDi
153
212
  });
154
213
 
155
214
  if (!result.success) {
156
- console.log('RAW LOGS:', JSON.stringify(result.logs, null, 2));
157
- console.log('OUTPUTS:', JSON.stringify(result.outputs?.map(o => o.path), null, 2));
158
- const errors = result.logs?.map(l => l.text || l.message || JSON.stringify(l)).join('\n') || 'Unknown error';
159
- throw new Error(`JavaScript bundling failed:\n${errors}`);
215
+ throw new Error(`Bundle failed\n${result.logs?.map(l => l.message).join('\n') || 'Unknown error'}`);
216
+ }
217
+
218
+ if (result.metafile) {
219
+ await Bun.write(join(outDir, 'metafile.json'), JSON.stringify(result.metafile, null, 2));
160
220
  }
161
221
 
162
- logger.success(`βœ… Bundled ${result.outputs.length} files`);
163
222
  return result;
164
223
 
165
224
  } finally {
@@ -167,15 +226,12 @@ async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDi
167
226
  }
168
227
  }
169
228
 
170
- function showBuildSummary(routes, serverIslands, clientRoutes, analyzedRoutes, durationMs) {
171
- logger.success(`✨ Build complete in ${durationMs.toFixed(1)}ms`);
172
- logger.bigLog('BUILD SUMMARY', { color: 'green' });
173
- logger.info(`πŸ“„ Total routes: ${routes.length}`);
174
- logger.info(`🏝️ Server Islands (SSG): ${serverIslands.length}`);
175
- logger.info(`⚑ Interactive (hydrated): ${analyzedRoutes.interactive.length}`);
176
- logger.info(`🧊 Static (no JS): ${analyzedRoutes.static.length}`);
177
- logger.info(`πŸ—ΊοΈ Sitemap: dist/sitemap.xml`);
178
- logger.info(`πŸ€– robots.txt: dist/robots.txt`);
179
- logger.info(`πŸ“Š Bundle report: dist/bundle-report.html`);
180
- logger.bigLog('READY TO DEPLOY πŸš€', { color: 'green' });
229
+ export async function build(options = {}) {
230
+ try {
231
+ await buildProduction(options);
232
+ } catch (error) {
233
+ console.error(error);
234
+ process.exit(1);
235
+ }
236
+ process.exit(0);
181
237
  }