bertui 1.2.1 β†’ 1.2.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/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,193 @@ 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;
100
+ await analyzeBuild(outDir, { outputFile: join(outDir, 'bundle-report.html') });
101
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'),
102
+ // ── Summary ──────────────────────────────────────────────────────────────
103
+ logger.printSummary({
104
+ routes: routes.length,
105
+ serverIslands: serverIslands.length,
106
+ interactive: analyzedRoutes.interactive.length,
107
+ staticRoutes: analyzedRoutes.static.length,
108
+ jsSize: `${totalKB} KB`,
109
+ outDir: 'dist/',
108
110
  });
109
111
 
110
- setTimeout(() => {
111
- logger.info('βœ… Build complete');
112
- process.exit(0);
113
- }, 100);
112
+ return { success: true };
114
113
 
115
114
  } catch (error) {
116
- logger.error(`Build failed: ${error.message}`);
117
- if (error.stack) logger.error(error.stack);
115
+ logger.stepFail('Build', error.message);
118
116
  if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
117
+ throw error;
118
+ }
119
+ }
120
+
121
+ // ─────────────────────────────────────────────────────────────────────────────
122
+
123
+ async function generateProductionImportMap(root, config) {
124
+ const importMap = {
125
+ 'react': 'https://esm.sh/react@18.2.0',
126
+ 'react-dom': 'https://esm.sh/react-dom@18.2.0',
127
+ 'react-dom/client': 'https://esm.sh/react-dom@18.2.0/client',
128
+ 'react/jsx-runtime': 'https://esm.sh/react@18.2.0/jsx-runtime',
129
+ };
119
130
 
120
- setTimeout(() => process.exit(1), 100);
131
+ const nodeModulesDir = join(root, 'node_modules');
132
+ if (!existsSync(nodeModulesDir)) return importMap;
133
+
134
+ try {
135
+ for (const pkg of readdirSync(nodeModulesDir)) {
136
+ if (!pkg.startsWith('bertui-') || pkg.startsWith('.')) continue;
137
+ const pkgDir = join(nodeModulesDir, pkg);
138
+ const pkgJson = join(pkgDir, 'package.json');
139
+ if (!existsSync(pkgJson)) continue;
140
+ try {
141
+ const p = JSON.parse(await Bun.file(pkgJson).text());
142
+ for (const entry of [p.browser, p.module, p.main, 'dist/index.js', 'index.js'].filter(Boolean)) {
143
+ if (existsSync(join(pkgDir, entry))) {
144
+ importMap[pkg] = `/assets/node_modules/${pkg}/${entry}`;
145
+ break;
146
+ }
147
+ }
148
+ } catch { continue; }
149
+ }
150
+ } catch { /* ignore */ }
151
+
152
+ return importMap;
153
+ }
154
+
155
+ async function copyNodeModulesToDist(root, outDir, importMap) {
156
+ const dest = join(outDir, 'assets', 'node_modules');
157
+ mkdirSync(dest, { recursive: true });
158
+ const src = join(root, 'node_modules');
159
+
160
+ for (const [, assetPath] of Object.entries(importMap)) {
161
+ if (assetPath.startsWith('https://')) continue;
162
+ const match = assetPath.match(/\/assets\/node_modules\/(.+)$/);
163
+ if (!match) continue;
164
+ const parts = match[1].split('/');
165
+ const pkgName = parts[0];
166
+ const subPath = parts.slice(1);
167
+ const srcFile = join(src, pkgName, ...subPath);
168
+ const destFile = join(dest, pkgName, ...subPath);
169
+ mkdirSync(join(dest, pkgName, ...subPath.slice(0, -1)), { recursive: true });
170
+ if (existsSync(srcFile)) await Bun.write(destFile, Bun.file(srcFile));
121
171
  }
122
172
  }
123
173
 
124
- async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir, analyzedRoutes) {
174
+ async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir, analyzedRoutes, importhow, root, config) {
125
175
  const originalCwd = process.cwd();
126
176
  process.chdir(buildDir);
127
177
 
128
178
  try {
129
- const hasRouter = existsSync(routerPath);
130
179
  const entrypoints = [buildEntry];
131
- if (hasRouter) entrypoints.push(routerPath);
180
+ if (existsSync(routerPath)) entrypoints.push(routerPath);
181
+
182
+ const importMap = await generateProductionImportMap(root, config);
183
+ await Bun.write(join(outDir, 'import-map.json'), JSON.stringify({ imports: importMap }, null, 2));
184
+ await copyNodeModulesToDist(root, outDir, importMap);
132
185
 
133
186
  const result = await Bun.build({
134
187
  entrypoints,
135
- outdir: join(outDir, 'assets'),
136
- target: 'browser',
137
- minify: true,
138
- splitting: true, // Code splitting per route
188
+ outdir: join(outDir, 'assets'),
189
+ target: 'browser',
190
+ format: 'esm',
191
+ minify: {
192
+ whitespace: true,
193
+ syntax: true,
194
+ identifiers: true,
195
+ },
196
+ splitting: true,
139
197
  sourcemap: 'external',
140
- metafile: true, // βœ… Enable for analyzer
198
+ metafile: true,
141
199
  naming: {
142
- entry: '[name]-[hash].js',
143
- chunk: 'chunks/[name]-[hash].js',
144
- asset: '[name]-[hash].[ext]',
200
+ entry: 'js/[name]-[hash].js',
201
+ chunk: 'js/chunks/[name]-[hash].js',
202
+ asset: 'assets/[name]-[hash].[ext]',
145
203
  },
146
- external: ['react', 'react-dom', 'react-dom/client'],
204
+ external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime'],
147
205
  define: {
148
206
  'process.env.NODE_ENV': '"production"',
149
207
  ...Object.fromEntries(
@@ -153,13 +211,13 @@ async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDi
153
211
  });
154
212
 
155
213
  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}`);
214
+ throw new Error(`Bundle failed\n${result.logs?.map(l => l.message).join('\n') || 'Unknown error'}`);
215
+ }
216
+
217
+ if (result.metafile) {
218
+ await Bun.write(join(outDir, 'metafile.json'), JSON.stringify(result.metafile, null, 2));
160
219
  }
161
220
 
162
- logger.success(`βœ… Bundled ${result.outputs.length} files`);
163
221
  return result;
164
222
 
165
223
  } finally {
@@ -167,15 +225,12 @@ async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDi
167
225
  }
168
226
  }
169
227
 
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' });
228
+ export async function build(options = {}) {
229
+ try {
230
+ await buildProduction(options);
231
+ process.exit(0);
232
+ } catch (error) {
233
+ console.error(error);
234
+ process.exit(1);
235
+ }
181
236
  }