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/README.md +152 -197
- package/index.js +16 -8
- package/package.json +1 -1
- package/src/build/compiler/file-transpiler.js +92 -141
- package/src/build/compiler/index.js +23 -15
- package/src/build.js +147 -92
- package/src/client/compiler.js +169 -157
- package/src/config/defaultConfig.js +13 -4
- package/src/config/loadConfig.js +47 -32
- package/src/dev.js +222 -49
- package/src/logger/logger.js +294 -16
- package/src/server/dev-handler.js +11 -0
- package/src/server/dev-server-utils.js +262 -160
- package/src/utils/importhow.js +52 -0
package/src/build.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// bertui/src/build.js
|
|
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
|
|
23
|
+
const root = options.root || process.cwd();
|
|
21
24
|
const buildDir = join(root, '.bertuibuild');
|
|
22
|
-
const outDir
|
|
25
|
+
const outDir = join(root, 'dist');
|
|
23
26
|
|
|
24
27
|
process.env.NODE_ENV = 'production';
|
|
25
28
|
|
|
26
|
-
logger.
|
|
29
|
+
logger.printHeader('BUILD');
|
|
27
30
|
|
|
28
31
|
if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
|
|
29
|
-
if (existsSync(outDir))
|
|
32
|
+
if (existsSync(outDir)) rmSync(outDir, { recursive: true, force: true });
|
|
30
33
|
mkdirSync(buildDir, { recursive: true });
|
|
31
|
-
mkdirSync(outDir,
|
|
34
|
+
mkdirSync(outDir, { recursive: true });
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
let totalKB = '0';
|
|
34
37
|
|
|
35
38
|
try {
|
|
36
|
-
|
|
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
|
|
41
|
-
|
|
42
|
-
logger.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
//
|
|
55
|
-
logger.
|
|
52
|
+
// ββ Step 3: Layouts ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
53
|
+
logger.step(3, TOTAL_STEPS, 'Layouts');
|
|
56
54
|
const layouts = await compileLayouts(root, buildDir);
|
|
57
|
-
|
|
58
|
-
if (layoutCount > 0) {
|
|
59
|
-
logger.success(`π ${layoutCount} layout(s) compiled`);
|
|
60
|
-
}
|
|
55
|
+
logger.stepDone('Layouts', `${Object.keys(layouts).length} found`);
|
|
61
56
|
|
|
62
|
-
//
|
|
63
|
-
logger.
|
|
64
|
-
|
|
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
|
-
//
|
|
67
|
-
logger.
|
|
62
|
+
// ββ Step 5: Hydration analysis βββββββββββββββββββββββββββββββββββββββββββ
|
|
63
|
+
logger.step(5, TOTAL_STEPS, 'Hydration analysis');
|
|
68
64
|
const analyzedRoutes = await analyzeRoutes(routes);
|
|
69
|
-
|
|
65
|
+
logger.stepDone('Hydration analysis',
|
|
66
|
+
`${analyzedRoutes.interactive.length} interactive Β· ${analyzedRoutes.static.length} static`);
|
|
70
67
|
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
100
|
-
const durationMs = Number(endTime - startTime) / 1_000_000;
|
|
100
|
+
await analyzeBuild(outDir, { outputFile: join(outDir, 'bundle-report.html') });
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
111
|
-
logger.info('β
Build complete');
|
|
112
|
-
process.exit(0);
|
|
113
|
-
}, 100);
|
|
112
|
+
return { success: true };
|
|
114
113
|
|
|
115
114
|
} catch (error) {
|
|
116
|
-
logger.
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
136
|
-
target:
|
|
137
|
-
|
|
138
|
-
|
|
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:
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
}
|