bertui 1.2.0 → 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 +119 -76
- package/src/build/compiler/index.js +23 -15
- package/src/build/compiler/route-discoverer.js +4 -3
- package/src/build/generators/sitemap-generator.js +1 -1
- package/src/build/processors/css-builder.js +45 -41
- package/src/build.js +147 -90
- 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
|
@@ -1,36 +1,60 @@
|
|
|
1
|
-
// bertui/src/build/compiler/file-transpiler.js
|
|
1
|
+
// bertui/src/build/compiler/file-transpiler.js
|
|
2
2
|
import { join, relative, dirname, extname } from 'path';
|
|
3
|
-
import { readdirSync, statSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
|
+
import { readdirSync, statSync, mkdirSync, writeFileSync, existsSync } from 'fs';
|
|
4
4
|
import logger from '../../logger/logger.js';
|
|
5
5
|
import { replaceEnvInCode } from '../../utils/env.js';
|
|
6
|
+
import { buildAliasMap, rewriteAliasImports } from '../../utils/importhow.js';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
writeFileSync(join(buildDir, 'bunfig.toml'), bunfigContent);
|
|
8
|
+
/**
|
|
9
|
+
* Compile src/ + alias dirs into buildDir.
|
|
10
|
+
*/
|
|
11
|
+
export async function compileBuildDirectory(srcDir, buildDir, root, envVars, importhow = {}) {
|
|
12
|
+
writeFileSync(
|
|
13
|
+
join(buildDir, 'bunfig.toml'),
|
|
14
|
+
`[build]\njsx = "react"\njsxFactory = "React.createElement"\njsxFragment = "React.Fragment"`.trim()
|
|
15
|
+
);
|
|
17
16
|
logger.info('Created bunfig.toml for classic JSX');
|
|
18
|
-
|
|
17
|
+
|
|
18
|
+
// Build mode: aliases resolve to buildDir/<alias> so relative paths inside dist/ are correct
|
|
19
|
+
const aliasMap = buildAliasMap(importhow, root, buildDir);
|
|
20
|
+
|
|
21
|
+
// Compile src/
|
|
22
|
+
await _compileDir(srcDir, buildDir, root, envVars, aliasMap);
|
|
23
|
+
|
|
24
|
+
// Compile each alias source dir → buildDir/<alias>
|
|
25
|
+
for (const [alias, relPath] of Object.entries(importhow)) {
|
|
26
|
+
const absAliasDir = join(root, relPath);
|
|
27
|
+
|
|
28
|
+
if (!existsSync(absAliasDir)) {
|
|
29
|
+
logger.warn(`⚠️ importhow alias "${alias}" points to missing dir: ${absAliasDir}`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const aliasOutDir = join(buildDir, alias);
|
|
34
|
+
mkdirSync(aliasOutDir, { recursive: true });
|
|
35
|
+
|
|
36
|
+
logger.info(`📦 Compiling alias dir [${alias}] → ${aliasOutDir}`);
|
|
37
|
+
await _compileDir(absAliasDir, aliasOutDir, root, envVars, aliasMap);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
async function _compileDir(srcDir, buildDir, root, envVars, aliasMap) {
|
|
19
44
|
const files = readdirSync(srcDir);
|
|
20
45
|
const filesToCompile = [];
|
|
21
|
-
|
|
46
|
+
|
|
22
47
|
for (const file of files) {
|
|
23
48
|
const srcPath = join(srcDir, file);
|
|
24
|
-
const stat
|
|
25
|
-
|
|
49
|
+
const stat = statSync(srcPath);
|
|
50
|
+
|
|
26
51
|
if (stat.isDirectory()) {
|
|
27
52
|
const subBuildDir = join(buildDir, file);
|
|
28
53
|
mkdirSync(subBuildDir, { recursive: true });
|
|
29
|
-
await
|
|
54
|
+
await _compileDir(srcPath, subBuildDir, root, envVars, aliasMap);
|
|
30
55
|
} else {
|
|
31
56
|
const ext = extname(file);
|
|
32
57
|
if (ext === '.css') continue;
|
|
33
|
-
|
|
34
58
|
if (['.jsx', '.tsx', '.ts'].includes(ext)) {
|
|
35
59
|
filesToCompile.push({ path: srcPath, dir: buildDir, name: file, type: 'tsx' });
|
|
36
60
|
} else if (ext === '.js') {
|
|
@@ -38,58 +62,67 @@ jsxFragment = "React.Fragment"
|
|
|
38
62
|
}
|
|
39
63
|
}
|
|
40
64
|
}
|
|
41
|
-
|
|
65
|
+
|
|
42
66
|
if (filesToCompile.length === 0) return;
|
|
43
|
-
|
|
44
|
-
logger.info(`📦 Compiling ${filesToCompile.length} files...`);
|
|
45
|
-
|
|
67
|
+
|
|
68
|
+
logger.info(`📦 Compiling ${filesToCompile.length} files in ${srcDir.split('/').slice(-2).join('/')}...`);
|
|
69
|
+
|
|
46
70
|
for (let i = 0; i < filesToCompile.length; i++) {
|
|
47
71
|
const file = filesToCompile[i];
|
|
48
|
-
|
|
49
72
|
try {
|
|
50
73
|
if (file.type === 'tsx') {
|
|
51
|
-
await
|
|
74
|
+
await _compileTSXFile(file.path, file.dir, file.name, root, envVars, buildDir, aliasMap);
|
|
52
75
|
} else {
|
|
53
|
-
await
|
|
76
|
+
await _compileJSFile(file.path, file.dir, file.name, root, envVars, aliasMap);
|
|
54
77
|
}
|
|
55
|
-
|
|
78
|
+
|
|
56
79
|
if ((i + 1) % 10 === 0 || i === filesToCompile.length - 1) {
|
|
57
|
-
const
|
|
58
|
-
logger.info(` Progress: ${i + 1}/${filesToCompile.length} (${
|
|
80
|
+
const pct = (((i + 1) / filesToCompile.length) * 100).toFixed(0);
|
|
81
|
+
logger.info(` Progress: ${i + 1}/${filesToCompile.length} (${pct}%)`);
|
|
59
82
|
}
|
|
60
|
-
|
|
61
83
|
} catch (error) {
|
|
62
84
|
logger.error(`Failed to compile ${file.name}: ${error.message}`);
|
|
63
85
|
}
|
|
64
86
|
}
|
|
65
|
-
|
|
87
|
+
|
|
66
88
|
logger.success(`✅ Compiled ${filesToCompile.length} files`);
|
|
67
89
|
}
|
|
68
90
|
|
|
69
|
-
|
|
91
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
92
|
+
// _rewriteNodeModuleImports — intentionally a no-op.
|
|
93
|
+
//
|
|
94
|
+
// Previously this rewrote bare specifiers like 'react' → '/node_modules/react/index.js'
|
|
95
|
+
// which caused "Could not resolve" errors during Bun.build because:
|
|
96
|
+
// 1. 'react' is marked `external` in Bun.build and expected as a bare specifier.
|
|
97
|
+
// 2. Other npm packages are better handled by Bun.build natively (tree-shaken + minified).
|
|
98
|
+
//
|
|
99
|
+
// Leaving bare specifiers untouched lets Bun.build do the right thing for both cases.
|
|
100
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
101
|
+
function _rewriteNodeModuleImports(code) {
|
|
102
|
+
return code;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function _compileTSXFile(srcPath, buildDir, filename, root, envVars, configDir, aliasMap) {
|
|
70
106
|
const ext = extname(filename);
|
|
71
|
-
|
|
107
|
+
|
|
72
108
|
try {
|
|
73
109
|
let code = await Bun.file(srcPath).text();
|
|
74
|
-
code =
|
|
110
|
+
code = _removeCSSImports(code);
|
|
75
111
|
code = replaceEnvInCode(code, envVars);
|
|
76
|
-
|
|
112
|
+
|
|
77
113
|
const outFilename = filename.replace(/\.(jsx|tsx|ts)$/, '.js');
|
|
78
|
-
const outPath
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
114
|
+
const outPath = join(buildDir, outFilename);
|
|
115
|
+
|
|
116
|
+
code = _fixBuildImports(code, srcPath, outPath, root);
|
|
117
|
+
|
|
82
118
|
if (!code.includes('import React')) {
|
|
83
119
|
code = `import React from 'react';\n${code}`;
|
|
84
120
|
}
|
|
85
|
-
|
|
86
|
-
// Use Bun.Transpiler with explicit production settings
|
|
121
|
+
|
|
87
122
|
const transpiler = new Bun.Transpiler({
|
|
88
123
|
loader: ext === '.tsx' ? 'tsx' : ext === '.ts' ? 'ts' : 'jsx',
|
|
89
124
|
target: 'browser',
|
|
90
|
-
define: {
|
|
91
|
-
'process.env.NODE_ENV': '"production"'
|
|
92
|
-
},
|
|
125
|
+
define: { 'process.env.NODE_ENV': '"production"' },
|
|
93
126
|
tsconfig: {
|
|
94
127
|
compilerOptions: {
|
|
95
128
|
jsx: 'react',
|
|
@@ -99,69 +132,79 @@ async function compileBuildFile(srcPath, buildDir, filename, root, envVars, conf
|
|
|
99
132
|
}
|
|
100
133
|
}
|
|
101
134
|
});
|
|
102
|
-
|
|
135
|
+
|
|
103
136
|
let compiled = await transpiler.transform(code);
|
|
104
|
-
|
|
105
|
-
// Verify no dev JSX leaked through
|
|
137
|
+
|
|
106
138
|
if (compiled.includes('jsxDEV')) {
|
|
107
|
-
logger.warn(`⚠️ Dev JSX
|
|
139
|
+
logger.warn(`⚠️ Dev JSX in ${filename}, fixing...`);
|
|
108
140
|
compiled = compiled.replace(/jsxDEV/g, 'jsx');
|
|
109
141
|
}
|
|
110
|
-
|
|
111
|
-
compiled =
|
|
142
|
+
|
|
143
|
+
compiled = _fixRelativeImports(compiled);
|
|
144
|
+
|
|
145
|
+
// ✅ Alias rewrite AFTER transpile — Bun won't undo it
|
|
146
|
+
compiled = rewriteAliasImports(compiled, outPath, aliasMap);
|
|
147
|
+
|
|
148
|
+
// NOTE: _rewriteNodeModuleImports is intentionally a no-op — bare specifiers
|
|
149
|
+
// are left for Bun.build to handle natively (tree-shaking + bundling).
|
|
150
|
+
compiled = _rewriteNodeModuleImports(compiled);
|
|
151
|
+
|
|
112
152
|
await Bun.write(outPath, compiled);
|
|
113
|
-
|
|
114
|
-
code = null;
|
|
115
|
-
compiled = null;
|
|
116
|
-
|
|
153
|
+
|
|
117
154
|
} catch (error) {
|
|
118
155
|
logger.error(`Failed to compile ${filename}: ${error.message}`);
|
|
119
156
|
throw error;
|
|
120
157
|
}
|
|
121
158
|
}
|
|
122
159
|
|
|
123
|
-
async function
|
|
160
|
+
async function _compileJSFile(srcPath, buildDir, filename, root, envVars, aliasMap) {
|
|
124
161
|
const outPath = join(buildDir, filename);
|
|
125
162
|
let code = await Bun.file(srcPath).text();
|
|
126
|
-
code =
|
|
163
|
+
code = _removeCSSImports(code);
|
|
127
164
|
code = replaceEnvInCode(code, envVars);
|
|
128
|
-
code =
|
|
129
|
-
|
|
130
|
-
|
|
165
|
+
code = _fixBuildImports(code, srcPath, outPath, root);
|
|
166
|
+
|
|
167
|
+
// JS files don't go through Bun.Transpiler so rewrite is safe here
|
|
168
|
+
code = rewriteAliasImports(code, outPath, aliasMap);
|
|
169
|
+
|
|
170
|
+
// NOTE: _rewriteNodeModuleImports is intentionally a no-op — see above.
|
|
171
|
+
code = _rewriteNodeModuleImports(code);
|
|
172
|
+
|
|
173
|
+
if (_usesJSX(code) && !code.includes('import React')) {
|
|
131
174
|
code = `import React from 'react';\n${code}`;
|
|
132
175
|
}
|
|
133
|
-
|
|
176
|
+
|
|
134
177
|
await Bun.write(outPath, code);
|
|
135
|
-
code = null;
|
|
136
178
|
}
|
|
137
179
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
180
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
function _usesJSX(code) {
|
|
183
|
+
return code.includes('React.createElement') ||
|
|
184
|
+
code.includes('React.Fragment') ||
|
|
141
185
|
/<[A-Z]/.test(code);
|
|
142
186
|
}
|
|
143
187
|
|
|
144
|
-
function
|
|
188
|
+
function _removeCSSImports(code) {
|
|
145
189
|
code = code.replace(/import\s+['"][^'"]*\.css['"];?\s*/g, '');
|
|
146
190
|
code = code.replace(/import\s+['"]bertui\/styles['"]\s*;?\s*/g, '');
|
|
147
191
|
return code;
|
|
148
192
|
}
|
|
149
193
|
|
|
150
|
-
function
|
|
151
|
-
const buildDir
|
|
152
|
-
const routerPath
|
|
153
|
-
const
|
|
154
|
-
const routerImport =
|
|
155
|
-
|
|
194
|
+
function _fixBuildImports(code, srcPath, outPath, root) {
|
|
195
|
+
const buildDir = join(root, '.bertuibuild');
|
|
196
|
+
const routerPath = join(buildDir, 'router.js');
|
|
197
|
+
const rel = relative(dirname(outPath), routerPath).replace(/\\/g, '/');
|
|
198
|
+
const routerImport = rel.startsWith('.') ? rel : './' + rel;
|
|
156
199
|
code = code.replace(/from\s+['"]bertui\/router['"]/g, `from '${routerImport}'`);
|
|
157
200
|
return code;
|
|
158
201
|
}
|
|
159
202
|
|
|
160
|
-
function
|
|
161
|
-
const importRegex = /from\s+['"](\.\.[
|
|
162
|
-
code = code.replace(importRegex, (match
|
|
163
|
-
if (
|
|
164
|
-
return
|
|
203
|
+
function _fixRelativeImports(code) {
|
|
204
|
+
const importRegex = /from\s+['"](\.\.[\\/]|\.\/)(?:[^'"]+?)(?<!\.js|\.jsx|\.ts|\.tsx|\.json)['"]/g;
|
|
205
|
+
code = code.replace(importRegex, (match) => {
|
|
206
|
+
if (/\.\w+['"]/.test(match)) return match;
|
|
207
|
+
return match.replace(/['"]$/, '.js"');
|
|
165
208
|
});
|
|
166
209
|
return code;
|
|
167
|
-
}
|
|
210
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// bertui/src/build/compiler/index.js
|
|
1
|
+
// bertui/src/build/compiler/index.js - WITH IMPORTHOW + NODE MODULE SUPPORT
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import logger from '../../logger/logger.js';
|
|
@@ -6,26 +6,33 @@ import { discoverRoutes } from './route-discoverer.js';
|
|
|
6
6
|
import { compileBuildDirectory } from './file-transpiler.js';
|
|
7
7
|
import { generateBuildRouter } from './router-generator.js';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} root
|
|
11
|
+
* @param {string} buildDir
|
|
12
|
+
* @param {Object} envVars
|
|
13
|
+
* @param {Object} config - full bertui config (includes importhow)
|
|
14
|
+
*/
|
|
15
|
+
export async function compileForBuild(root, buildDir, envVars, config = {}) {
|
|
16
|
+
const srcDir = join(root, 'src');
|
|
12
17
|
const pagesDir = join(srcDir, 'pages');
|
|
13
|
-
|
|
18
|
+
|
|
14
19
|
if (!existsSync(srcDir)) {
|
|
15
20
|
throw new Error('src/ directory not found!');
|
|
16
21
|
}
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
|
|
23
|
+
const importhow = config.importhow || {};
|
|
24
|
+
|
|
25
|
+
let routes = [];
|
|
19
26
|
let serverIslands = [];
|
|
20
|
-
let clientRoutes
|
|
21
|
-
|
|
27
|
+
let clientRoutes = [];
|
|
28
|
+
|
|
22
29
|
if (existsSync(pagesDir)) {
|
|
23
30
|
routes = await discoverRoutes(pagesDir);
|
|
24
|
-
|
|
31
|
+
|
|
25
32
|
for (const route of routes) {
|
|
26
33
|
const sourceCode = await Bun.file(route.path).text();
|
|
27
34
|
const isServerIsland = sourceCode.includes('export const render = "server"');
|
|
28
|
-
|
|
35
|
+
|
|
29
36
|
if (isServerIsland) {
|
|
30
37
|
serverIslands.push(route);
|
|
31
38
|
logger.success(`🏝️ Server Island: ${route.route}`);
|
|
@@ -34,12 +41,13 @@ export async function compileForBuild(root, buildDir, envVars) {
|
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
43
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
|
|
45
|
+
// Pass importhow so alias dirs also get compiled
|
|
46
|
+
await compileBuildDirectory(srcDir, buildDir, root, envVars, importhow);
|
|
47
|
+
|
|
40
48
|
if (routes.length > 0) {
|
|
41
49
|
await generateBuildRouter(routes, buildDir);
|
|
42
50
|
}
|
|
43
|
-
|
|
51
|
+
|
|
44
52
|
return { routes, serverIslands, clientRoutes };
|
|
45
53
|
}
|
|
@@ -20,13 +20,14 @@ export async function discoverRoutes(pagesDir) {
|
|
|
20
20
|
|
|
21
21
|
if (['.jsx', '.tsx', '.js', '.ts'].includes(ext)) {
|
|
22
22
|
const fileName = entry.name.replace(ext, '');
|
|
23
|
+
|
|
24
|
+
// Only loading is reserved - index is a valid route (becomes /)
|
|
25
|
+
if (fileName === 'loading') continue;
|
|
26
|
+
|
|
23
27
|
let route = '/' + relativePath.replace(/\\/g, '/').replace(ext, '');
|
|
24
|
-
|
|
25
|
-
const RESERVED = ['index', 'loading'];
|
|
26
28
|
if (fileName === 'index') {
|
|
27
29
|
route = route.replace('/index', '') || '/';
|
|
28
30
|
}
|
|
29
|
-
if (RESERVED.includes(fileName)) continue;
|
|
30
31
|
|
|
31
32
|
const isDynamic = fileName.includes('[') && fileName.includes(']');
|
|
32
33
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// bertui/src/build/processors/css-builder.js - WITH SCSS + CACHING
|
|
1
|
+
// bertui/src/build/processors/css-builder.js - WITH SCSS + CACHING + CSS MODULES
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync, readdirSync, mkdirSync } from 'fs';
|
|
4
4
|
import logger from '../../logger/logger.js';
|
|
@@ -9,13 +9,14 @@ export async function buildAllCSS(root, outDir) {
|
|
|
9
9
|
const startTime = process.hrtime.bigint();
|
|
10
10
|
|
|
11
11
|
const srcStylesDir = join(root, 'src', 'styles');
|
|
12
|
+
// CSS modules scoped CSS is staged here by file-transpiler.js
|
|
13
|
+
const modulesStagingDir = join(root, '.bertuibuild', 'styles-staged');
|
|
12
14
|
const stylesOutDir = join(outDir, 'styles');
|
|
13
15
|
|
|
14
16
|
mkdirSync(stylesOutDir, { recursive: true });
|
|
15
17
|
|
|
16
|
-
// Check cache for entire CSS build
|
|
17
18
|
const cacheKey = `css-build:${root}:${Date.now()}`;
|
|
18
|
-
const cached = globalCache.get(cacheKey, { ttl: 1000 });
|
|
19
|
+
const cached = globalCache.get(cacheKey, { ttl: 1000 });
|
|
19
20
|
|
|
20
21
|
if (cached) {
|
|
21
22
|
logger.info(`⚡ Using cached CSS (${cached.files} files)`);
|
|
@@ -23,43 +24,50 @@ export async function buildAllCSS(root, outDir) {
|
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
let combinedCSS = '';
|
|
28
|
+
let fileCount = 0;
|
|
29
|
+
|
|
30
|
+
// 1. Process src/styles/ (plain CSS + SCSS)
|
|
31
|
+
if (existsSync(srcStylesDir)) {
|
|
32
|
+
await processSCSSDirectory(srcStylesDir, root);
|
|
33
|
+
const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css') && !f.endsWith('.module.css'));
|
|
34
|
+
|
|
35
|
+
logger.info(`Processing ${cssFiles.length} CSS file(s)...`);
|
|
36
|
+
|
|
37
|
+
for (const cssFile of cssFiles) {
|
|
38
|
+
const srcPath = join(srcStylesDir, cssFile);
|
|
39
|
+
const fileBuffer = await globalCache.getFile(srcPath, { logSpeed: true });
|
|
40
|
+
if (fileBuffer) {
|
|
41
|
+
const content = fileBuffer.toString('utf-8');
|
|
42
|
+
combinedCSS += `/* ${cssFile} */\n${content}\n\n`;
|
|
43
|
+
fileCount++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
logger.info('No styles directory found');
|
|
30
48
|
}
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
|
|
50
|
+
// 2. Include scoped CSS from CSS modules (staged by file-transpiler.js)
|
|
51
|
+
if (existsSync(modulesStagingDir)) {
|
|
52
|
+
const moduleFiles = readdirSync(modulesStagingDir).filter(f => f.endsWith('.css'));
|
|
53
|
+
if (moduleFiles.length > 0) {
|
|
54
|
+
logger.info(`Including ${moduleFiles.length} CSS module(s)...`);
|
|
55
|
+
for (const cssFile of moduleFiles) {
|
|
56
|
+
const srcPath = join(modulesStagingDir, cssFile);
|
|
57
|
+
const content = await Bun.file(srcPath).text();
|
|
58
|
+
combinedCSS += `/* module: ${cssFile} */\n${content}\n\n`;
|
|
59
|
+
fileCount++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!combinedCSS.trim()) {
|
|
39
65
|
await Bun.write(join(stylesOutDir, 'bertui.min.css'), '/* No CSS */');
|
|
40
66
|
return;
|
|
41
67
|
}
|
|
42
|
-
|
|
43
|
-
logger.info(`Processing ${cssFiles.length} CSS file(s)...`);
|
|
44
|
-
|
|
45
|
-
let combinedCSS = '';
|
|
46
|
-
const fileContents = [];
|
|
47
|
-
|
|
48
|
-
for (const cssFile of cssFiles) {
|
|
49
|
-
const srcPath = join(srcStylesDir, cssFile);
|
|
50
|
-
|
|
51
|
-
// Use file cache
|
|
52
|
-
const fileBuffer = await globalCache.getFile(srcPath, { logSpeed: true });
|
|
53
|
-
if (fileBuffer) {
|
|
54
|
-
const content = fileBuffer.toString('utf-8');
|
|
55
|
-
fileContents.push({ filename: cssFile, content });
|
|
56
|
-
combinedCSS += `/* ${cssFile} */\n${content}\n\n`;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
68
|
+
|
|
60
69
|
const combinedPath = join(stylesOutDir, 'bertui.min.css');
|
|
61
70
|
|
|
62
|
-
// Minify with caching
|
|
63
71
|
const minifyCacheKey = `minify:${Buffer.from(combinedCSS).length}:${combinedCSS.substring(0, 100)}`;
|
|
64
72
|
let minified = globalCache.get(minifyCacheKey);
|
|
65
73
|
|
|
@@ -68,7 +76,7 @@ export async function buildAllCSS(root, outDir) {
|
|
|
68
76
|
filename: 'bertui.min.css',
|
|
69
77
|
sourceMap: false
|
|
70
78
|
});
|
|
71
|
-
globalCache.set(minifyCacheKey, minified, { ttl: 60000 });
|
|
79
|
+
globalCache.set(minifyCacheKey, minified, { ttl: 60000 });
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
await Bun.write(combinedPath, minified);
|
|
@@ -78,23 +86,20 @@ export async function buildAllCSS(root, outDir) {
|
|
|
78
86
|
const reduction = ((1 - minifiedSize / originalSize) * 100).toFixed(1);
|
|
79
87
|
|
|
80
88
|
const endTime = process.hrtime.bigint();
|
|
81
|
-
const duration = Number(endTime - startTime) / 1000;
|
|
89
|
+
const duration = Number(endTime - startTime) / 1000;
|
|
82
90
|
|
|
83
91
|
logger.success(`CSS optimized: ${(originalSize/1024).toFixed(2)}KB → ${(minifiedSize/1024).toFixed(2)}KB (-${reduction}%)`);
|
|
84
92
|
logger.info(`⚡ Processing time: ${duration.toFixed(3)}µs`);
|
|
85
93
|
|
|
86
|
-
// Cache the final result
|
|
87
94
|
globalCache.set(cacheKey, {
|
|
88
|
-
files:
|
|
95
|
+
files: fileCount,
|
|
89
96
|
content: minified,
|
|
90
97
|
size: minifiedSize
|
|
91
98
|
}, { ttl: 5000 });
|
|
92
99
|
}
|
|
93
100
|
|
|
94
|
-
// NEW: Process SCSS directory
|
|
95
101
|
async function processSCSSDirectory(stylesDir, root) {
|
|
96
102
|
try {
|
|
97
|
-
// Check if sass is installed
|
|
98
103
|
const sass = await import('sass').catch(() => null);
|
|
99
104
|
if (!sass) return;
|
|
100
105
|
|
|
@@ -109,7 +114,6 @@ async function processSCSSDirectory(stylesDir, root) {
|
|
|
109
114
|
const srcPath = join(stylesDir, file);
|
|
110
115
|
const cssPath = join(stylesDir, file.replace(/\.(scss|sass)$/, '.css'));
|
|
111
116
|
|
|
112
|
-
// Check cache
|
|
113
117
|
const fileBuffer = await globalCache.getFile(srcPath);
|
|
114
118
|
const cacheKey = `scss:${file}:${Buffer.from(fileBuffer).length}`;
|
|
115
119
|
const cached = globalCache.get(cacheKey);
|