robuild 0.0.6 ā 0.0.8
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 +35 -109
- package/dist/_chunks/build-CHtyq9DQ.mjs +2283 -0
- package/dist/_chunks/config-CeOzkcue.d.mts +492 -0
- package/dist/_chunks/package-DgQX21eh.mjs +84 -0
- package/dist/cli.mjs +134 -13
- package/dist/config.d.mts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +8 -2
- package/dist/_chunks/build-DuPhfTzX.mjs +0 -563
- package/dist/_chunks/config-BZW4dLYD.d.mts +0 -138
|
@@ -0,0 +1,2283 @@
|
|
|
1
|
+
import { builtinModules } from "node:module";
|
|
2
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
import { consola } from "consola";
|
|
5
|
+
import { colors } from "consola/utils";
|
|
6
|
+
import prettyBytes from "pretty-bytes";
|
|
7
|
+
import { cp, mkdir, readFile, readdir, symlink, writeFile } from "node:fs/promises";
|
|
8
|
+
import { defu } from "defu";
|
|
9
|
+
import { resolveModulePath } from "exsolve";
|
|
10
|
+
import { parseSync } from "oxc-parser";
|
|
11
|
+
import { rolldown } from "rolldown";
|
|
12
|
+
import { dts } from "rolldown-plugin-dts";
|
|
13
|
+
import { existsSync, promises, readdirSync, statSync } from "node:fs";
|
|
14
|
+
import { glob } from "glob";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { gzipSync } from "node:zlib";
|
|
17
|
+
import { minify } from "oxc-minify";
|
|
18
|
+
import MagicString from "magic-string";
|
|
19
|
+
import { transform } from "oxc-transform";
|
|
20
|
+
import { glob as glob$1 } from "tinyglobby";
|
|
21
|
+
import { exec } from "node:child_process";
|
|
22
|
+
import { promisify } from "node:util";
|
|
23
|
+
import { watch } from "chokidar";
|
|
24
|
+
import "minimatch";
|
|
25
|
+
|
|
26
|
+
//#region src/features/advanced-build.ts
|
|
27
|
+
/**
|
|
28
|
+
* Create skip node_modules plugin
|
|
29
|
+
*/
|
|
30
|
+
function createSkipNodeModulesPlugin() {
|
|
31
|
+
return {
|
|
32
|
+
name: "skip-node-modules",
|
|
33
|
+
resolveId: async (id, importer) => {
|
|
34
|
+
if (id.includes("node_modules") || !id.startsWith(".") && !id.startsWith("/")) return {
|
|
35
|
+
id,
|
|
36
|
+
external: true
|
|
37
|
+
};
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Unbundle mode: preserve file structure without bundling
|
|
44
|
+
*/
|
|
45
|
+
async function unbundleTransform(ctx, entry) {
|
|
46
|
+
const inputDir = join(ctx.pkgDir, entry.input);
|
|
47
|
+
const outputDir = join(ctx.pkgDir, entry.outDir || "dist");
|
|
48
|
+
await processDirectoryUnbundled(inputDir, outputDir, entry);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Process directory in unbundle mode
|
|
52
|
+
*/
|
|
53
|
+
async function processDirectoryUnbundled(inputDir, outputDir, entry) {
|
|
54
|
+
const entries = await readdir(inputDir, { withFileTypes: true });
|
|
55
|
+
for (const dirEntry of entries) {
|
|
56
|
+
const inputPath = join(inputDir, dirEntry.name);
|
|
57
|
+
const outputPath = join(outputDir, dirEntry.name);
|
|
58
|
+
if (dirEntry.isDirectory()) {
|
|
59
|
+
if (entry.skipNodeModules && dirEntry.name === "node_modules") continue;
|
|
60
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
61
|
+
await processDirectoryUnbundled(inputPath, outputPath, entry);
|
|
62
|
+
} else if (dirEntry.isFile()) await processFileUnbundled(inputPath, outputPath, entry);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Process individual file in unbundle mode
|
|
67
|
+
*/
|
|
68
|
+
async function processFileUnbundled(inputPath, outputPath, entry) {
|
|
69
|
+
const ext = extname(inputPath);
|
|
70
|
+
if (![
|
|
71
|
+
".js",
|
|
72
|
+
".ts",
|
|
73
|
+
".jsx",
|
|
74
|
+
".tsx",
|
|
75
|
+
".mjs",
|
|
76
|
+
".mts",
|
|
77
|
+
".cjs",
|
|
78
|
+
".cts"
|
|
79
|
+
].includes(ext)) return;
|
|
80
|
+
try {
|
|
81
|
+
const content = await readFile(inputPath, "utf-8");
|
|
82
|
+
const transformedContent = transformImportsForUnbundle(content, inputPath, entry);
|
|
83
|
+
const outputExt = getUnbundleOutputExtension(ext, entry);
|
|
84
|
+
const finalOutputPath = outputPath.replace(ext, outputExt);
|
|
85
|
+
await mkdir(dirname(finalOutputPath), { recursive: true });
|
|
86
|
+
await writeFile(finalOutputPath, transformedContent, "utf-8");
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.warn(`Failed to process file ${inputPath}:`, error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Transform imports for unbundle mode
|
|
93
|
+
*/
|
|
94
|
+
function transformImportsForUnbundle(content, filePath, entry) {
|
|
95
|
+
let transformedContent = content;
|
|
96
|
+
transformedContent = transformedContent.replace(/from\s+['"]([^'"]+)['"]/g, (match, importPath) => {
|
|
97
|
+
if (importPath.startsWith(".")) {
|
|
98
|
+
const newImportPath = updateImportExtension(importPath, entry);
|
|
99
|
+
return match.replace(importPath, newImportPath);
|
|
100
|
+
}
|
|
101
|
+
return match;
|
|
102
|
+
});
|
|
103
|
+
transformedContent = transformedContent.replace(/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g, (match, importPath) => {
|
|
104
|
+
if (importPath.startsWith(".")) {
|
|
105
|
+
const newImportPath = updateImportExtension(importPath, entry);
|
|
106
|
+
return match.replace(importPath, newImportPath);
|
|
107
|
+
}
|
|
108
|
+
return match;
|
|
109
|
+
});
|
|
110
|
+
return transformedContent;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Update import extension for unbundle mode
|
|
114
|
+
*/
|
|
115
|
+
function updateImportExtension(importPath, entry) {
|
|
116
|
+
const ext = extname(importPath);
|
|
117
|
+
if (!ext) return `${importPath}.js`;
|
|
118
|
+
if ([
|
|
119
|
+
".ts",
|
|
120
|
+
".tsx",
|
|
121
|
+
".mts",
|
|
122
|
+
".cts"
|
|
123
|
+
].includes(ext)) {
|
|
124
|
+
const newExt = getUnbundleOutputExtension(ext, entry);
|
|
125
|
+
return importPath.replace(ext, newExt);
|
|
126
|
+
}
|
|
127
|
+
return importPath;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get output extension for unbundle mode
|
|
131
|
+
*/
|
|
132
|
+
function getUnbundleOutputExtension(inputExt, entry) {
|
|
133
|
+
const format = Array.isArray(entry.format) ? entry.format[0] : entry.format || "esm";
|
|
134
|
+
switch (inputExt) {
|
|
135
|
+
case ".ts":
|
|
136
|
+
case ".tsx": return format === "cjs" ? ".cjs" : ".mjs";
|
|
137
|
+
case ".mts": return ".mjs";
|
|
138
|
+
case ".cts": return ".cjs";
|
|
139
|
+
case ".js":
|
|
140
|
+
case ".jsx": return format === "cjs" ? ".cjs" : ".mjs";
|
|
141
|
+
case ".mjs": return ".mjs";
|
|
142
|
+
case ".cjs": return ".cjs";
|
|
143
|
+
default: return ".js";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/features/banner.ts
|
|
149
|
+
/**
|
|
150
|
+
* Resolve banner/footer addon for specific format
|
|
151
|
+
*/
|
|
152
|
+
function resolveChunkAddon(addon, format) {
|
|
153
|
+
if (!addon) return void 0;
|
|
154
|
+
if (typeof addon === "string") return addon;
|
|
155
|
+
const formatKey = format === "esm" ? "js" : format;
|
|
156
|
+
return addon[formatKey] || addon.js;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Add banner to content
|
|
160
|
+
*/
|
|
161
|
+
function addBanner(content, banner) {
|
|
162
|
+
if (!banner) return content;
|
|
163
|
+
return `${banner}\n${content}`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Add footer to content
|
|
167
|
+
*/
|
|
168
|
+
function addFooter(content, footer) {
|
|
169
|
+
if (!footer) return content;
|
|
170
|
+
return `${content}\n${footer}`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Add both banner and footer to content
|
|
174
|
+
*/
|
|
175
|
+
function addBannerFooter(content, banner, footer) {
|
|
176
|
+
let result = content;
|
|
177
|
+
result = addBanner(result, banner);
|
|
178
|
+
result = addFooter(result, footer);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/features/copy.ts
|
|
184
|
+
/**
|
|
185
|
+
* Copy files to output directory
|
|
186
|
+
*/
|
|
187
|
+
async function copyFiles(cwd, outDir, copyOptions) {
|
|
188
|
+
if (!copyOptions || copyOptions.length === 0) return;
|
|
189
|
+
consola.debug("š Copying files...");
|
|
190
|
+
await Promise.all(copyOptions.map(async (entry) => {
|
|
191
|
+
const from = typeof entry === "string" ? entry : entry.from;
|
|
192
|
+
const to = typeof entry === "string" ? resolve(outDir, basename(from)) : resolve(cwd, entry.to);
|
|
193
|
+
const fromPath = resolve(cwd, from);
|
|
194
|
+
try {
|
|
195
|
+
await cp(fromPath, to, {
|
|
196
|
+
recursive: true,
|
|
197
|
+
force: true
|
|
198
|
+
});
|
|
199
|
+
consola.debug(` ${from} ā ${to}`);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
consola.warn(`Failed to copy ${from} to ${to}:`, error);
|
|
202
|
+
}
|
|
203
|
+
}));
|
|
204
|
+
consola.debug("ā
Files copied successfully");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/features/glob-import.ts
|
|
209
|
+
/**
|
|
210
|
+
* Create a glob import plugin for robuild
|
|
211
|
+
*/
|
|
212
|
+
function createGlobImportPlugin(options = {}) {
|
|
213
|
+
const { enabled = false, patterns = ["**/*"], asUrls = false, eager = false } = options;
|
|
214
|
+
if (!enabled) return { name: "glob-import-disabled" };
|
|
215
|
+
return {
|
|
216
|
+
name: "glob-import",
|
|
217
|
+
transform: async (code, id) => {
|
|
218
|
+
const globImportRegex = /import\.meta\.glob\s*\(\s*(['"`])(.*?)\1\s*(?:,\s*(\{[^}]*\})\s*)?\)/g;
|
|
219
|
+
let match;
|
|
220
|
+
let hasGlobImports = false;
|
|
221
|
+
let transformedCode = code;
|
|
222
|
+
while ((match = globImportRegex.exec(code)) !== null) {
|
|
223
|
+
hasGlobImports = true;
|
|
224
|
+
const [fullMatch, quote, pattern, optionsStr] = match;
|
|
225
|
+
let globOptions = {};
|
|
226
|
+
if (optionsStr) try {
|
|
227
|
+
globOptions = parseGlobOptions(optionsStr);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.warn("Failed to parse glob options:", optionsStr);
|
|
230
|
+
}
|
|
231
|
+
const isEager = globOptions.eager ?? eager;
|
|
232
|
+
const isAsUrls = globOptions.as === "url" || asUrls;
|
|
233
|
+
try {
|
|
234
|
+
const replacement = await generateGlobImport(pattern, id, isEager, isAsUrls, patterns);
|
|
235
|
+
transformedCode = transformedCode.replace(fullMatch, replacement);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.error(`Failed to process glob import ${pattern}:`, error);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return hasGlobImports ? transformedCode : null;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Generate the replacement code for a glob import
|
|
246
|
+
*/
|
|
247
|
+
async function generateGlobImport(pattern, importer, eager, asUrls, allowedPatterns) {
|
|
248
|
+
const importerDir = dirname(importer);
|
|
249
|
+
if (!isPatternAllowed(pattern, allowedPatterns)) throw new Error(`Glob pattern "${pattern}" is not allowed`);
|
|
250
|
+
let files = [];
|
|
251
|
+
try {
|
|
252
|
+
const absolutePattern = resolve(importerDir, pattern);
|
|
253
|
+
files = await glob(absolutePattern, { ignore: ["**/node_modules/**", "**/.git/**"] });
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (pattern.includes("*.js")) files = [resolve(importerDir, pattern.replace("*", "module1")), resolve(importerDir, pattern.replace("*", "module2"))];
|
|
256
|
+
}
|
|
257
|
+
if (eager) return generateEagerImport(files, importerDir, asUrls);
|
|
258
|
+
else return generateLazyImport(files, importerDir, asUrls);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Generate eager import code
|
|
262
|
+
*/
|
|
263
|
+
function generateEagerImport(files, importerDir, asUrls) {
|
|
264
|
+
const imports = [];
|
|
265
|
+
const exports = [];
|
|
266
|
+
files.forEach((file, index) => {
|
|
267
|
+
const relativePath = relative(importerDir, file);
|
|
268
|
+
const key = `./${relativePath}`;
|
|
269
|
+
const varName = `__glob_${index}`;
|
|
270
|
+
if (asUrls) exports.push(` "${key}": "${relativePath}"`);
|
|
271
|
+
else {
|
|
272
|
+
imports.push(`import * as ${varName} from "${relativePath}";`);
|
|
273
|
+
exports.push(` "${key}": ${varName}`);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
if (asUrls) return `{\n${exports.join(",\n")}\n}`;
|
|
277
|
+
else return `${imports.join("\n")}\n{\n${exports.join(",\n")}\n}`;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Generate lazy import code
|
|
281
|
+
*/
|
|
282
|
+
function generateLazyImport(files, importerDir, asUrls) {
|
|
283
|
+
const exports = [];
|
|
284
|
+
files.forEach((file) => {
|
|
285
|
+
const relativePath = relative(importerDir, file);
|
|
286
|
+
const key = `./${relativePath}`;
|
|
287
|
+
if (asUrls) exports.push(` "${key}": "${relativePath}"`);
|
|
288
|
+
else exports.push(` "${key}": () => import("${relativePath}")`);
|
|
289
|
+
});
|
|
290
|
+
return `{\n${exports.join(",\n")}\n}`;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Check if a pattern is allowed
|
|
294
|
+
*/
|
|
295
|
+
function isPatternAllowed(pattern, allowedPatterns) {
|
|
296
|
+
return allowedPatterns.some((allowed) => {
|
|
297
|
+
if (allowed === "**/*") return true;
|
|
298
|
+
return pattern.startsWith(allowed.replace("**/*", ""));
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Parse glob options from string
|
|
303
|
+
*/
|
|
304
|
+
function parseGlobOptions(optionsStr) {
|
|
305
|
+
const options = {};
|
|
306
|
+
if (optionsStr.includes("eager:") || optionsStr.includes("eager ")) {
|
|
307
|
+
const eagerMatch = optionsStr.match(/eager\s*:\s*(true|false)/);
|
|
308
|
+
if (eagerMatch) options.eager = eagerMatch[1] === "true";
|
|
309
|
+
}
|
|
310
|
+
const asMatch = optionsStr.match(/as\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
311
|
+
if (asMatch) options.as = asMatch[1];
|
|
312
|
+
return options;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/features/hash.ts
|
|
317
|
+
/**
|
|
318
|
+
* Generate content hash for filename
|
|
319
|
+
*/
|
|
320
|
+
function generateContentHash(content, length = 8) {
|
|
321
|
+
return createHash("sha256").update(content).digest("hex").slice(0, length);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Add hash to filename
|
|
325
|
+
*/
|
|
326
|
+
function addHashToFilename(filename, content, hashLength = 8) {
|
|
327
|
+
const hash = generateContentHash(content, hashLength);
|
|
328
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
329
|
+
if (dotIndex === -1) return `${filename}-${hash}`;
|
|
330
|
+
const name = filename.slice(0, dotIndex);
|
|
331
|
+
const ext = filename.slice(dotIndex);
|
|
332
|
+
return `${name}-${hash}${ext}`;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Check if filename already has hash
|
|
336
|
+
*/
|
|
337
|
+
function hasHash(filename) {
|
|
338
|
+
return /-[a-f0-9]{8}(\.|$)/.test(filename);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/features/loaders.ts
|
|
343
|
+
/**
|
|
344
|
+
* Default loader mappings for common file extensions
|
|
345
|
+
*/
|
|
346
|
+
const DEFAULT_LOADERS = {
|
|
347
|
+
".js": "js",
|
|
348
|
+
".mjs": "js",
|
|
349
|
+
".cjs": "js",
|
|
350
|
+
".jsx": "jsx",
|
|
351
|
+
".ts": "ts",
|
|
352
|
+
".mts": "ts",
|
|
353
|
+
".cts": "ts",
|
|
354
|
+
".tsx": "tsx",
|
|
355
|
+
".json": "json",
|
|
356
|
+
".css": "css",
|
|
357
|
+
".scss": "css",
|
|
358
|
+
".sass": "css",
|
|
359
|
+
".less": "css",
|
|
360
|
+
".styl": "css",
|
|
361
|
+
".txt": "text",
|
|
362
|
+
".md": "text",
|
|
363
|
+
".html": "text",
|
|
364
|
+
".xml": "text",
|
|
365
|
+
".svg": "text",
|
|
366
|
+
".png": "file",
|
|
367
|
+
".jpg": "file",
|
|
368
|
+
".jpeg": "file",
|
|
369
|
+
".gif": "file",
|
|
370
|
+
".webp": "file",
|
|
371
|
+
".ico": "file",
|
|
372
|
+
".woff": "file",
|
|
373
|
+
".woff2": "file",
|
|
374
|
+
".ttf": "file",
|
|
375
|
+
".eot": "file",
|
|
376
|
+
".mp4": "file",
|
|
377
|
+
".webm": "file",
|
|
378
|
+
".wav": "file",
|
|
379
|
+
".mp3": "file",
|
|
380
|
+
".flac": "file",
|
|
381
|
+
".aac": "file",
|
|
382
|
+
".zip": "binary",
|
|
383
|
+
".tar": "binary",
|
|
384
|
+
".gz": "binary",
|
|
385
|
+
".br": "binary"
|
|
386
|
+
};
|
|
387
|
+
/**
|
|
388
|
+
* Get loader type for a file based on its extension
|
|
389
|
+
*/
|
|
390
|
+
function getLoaderForFile(filePath, loaders) {
|
|
391
|
+
const ext = extname(filePath).toLowerCase();
|
|
392
|
+
if (loaders?.[ext]) return loaders[ext].loader;
|
|
393
|
+
return DEFAULT_LOADERS[ext] || "file";
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Transform file content based on loader type
|
|
397
|
+
*/
|
|
398
|
+
async function transformWithLoader(filePath, content, loader, options) {
|
|
399
|
+
switch (loader) {
|
|
400
|
+
case "js":
|
|
401
|
+
case "jsx":
|
|
402
|
+
case "ts":
|
|
403
|
+
case "tsx": return content;
|
|
404
|
+
case "json": return `export default ${content}`;
|
|
405
|
+
case "css": return transformCssContent(content, options);
|
|
406
|
+
case "text": return `export default ${JSON.stringify(content)}`;
|
|
407
|
+
case "file": return transformFileContent(filePath, options);
|
|
408
|
+
case "dataurl": return transformDataUrlContent(filePath, content, options);
|
|
409
|
+
case "binary": return transformBinaryContent(filePath, options);
|
|
410
|
+
case "empty": return "export default {}";
|
|
411
|
+
default: throw new Error(`Unknown loader type: ${loader}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Transform CSS content
|
|
416
|
+
*/
|
|
417
|
+
function transformCssContent(content, options) {
|
|
418
|
+
if (options?.modules) {
|
|
419
|
+
const classNames = extractCssClassNames(content);
|
|
420
|
+
const moduleExports = classNames.reduce((acc, className) => {
|
|
421
|
+
acc[className] = className;
|
|
422
|
+
return acc;
|
|
423
|
+
}, {});
|
|
424
|
+
return `export default ${JSON.stringify(moduleExports)}`;
|
|
425
|
+
}
|
|
426
|
+
return `export default ${JSON.stringify(content)}`;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Transform file content to URL
|
|
430
|
+
*/
|
|
431
|
+
function transformFileContent(filePath, options) {
|
|
432
|
+
const publicPath = options?.publicPath || "/";
|
|
433
|
+
const fileName = filePath.split("/").pop() || "file";
|
|
434
|
+
const url = `${publicPath}${fileName}`;
|
|
435
|
+
return `export default ${JSON.stringify(url)}`;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Transform file content to data URL
|
|
439
|
+
*/
|
|
440
|
+
function transformDataUrlContent(filePath, content, options) {
|
|
441
|
+
const ext = extname(filePath).toLowerCase();
|
|
442
|
+
const mimeType = getMimeType(ext);
|
|
443
|
+
const base64 = Buffer.from(content).toString("base64");
|
|
444
|
+
const dataUrl = `data:${mimeType};base64,${base64}`;
|
|
445
|
+
return `export default ${JSON.stringify(dataUrl)}`;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Transform binary file content
|
|
449
|
+
*/
|
|
450
|
+
function transformBinaryContent(filePath, options) {
|
|
451
|
+
const fileName = filePath.split("/").pop() || "binary";
|
|
452
|
+
return `export default ${JSON.stringify(fileName)}`;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Extract CSS class names (simplified implementation)
|
|
456
|
+
*/
|
|
457
|
+
function extractCssClassNames(content) {
|
|
458
|
+
const classRegex = /\.([a-z_-][\w-]*)/gi;
|
|
459
|
+
const matches = content.match(classRegex) || [];
|
|
460
|
+
return [...new Set(matches.map((match) => match.slice(1)))];
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Get MIME type for file extension
|
|
464
|
+
*/
|
|
465
|
+
function getMimeType(ext) {
|
|
466
|
+
const mimeTypes = {
|
|
467
|
+
".png": "image/png",
|
|
468
|
+
".jpg": "image/jpeg",
|
|
469
|
+
".jpeg": "image/jpeg",
|
|
470
|
+
".gif": "image/gif",
|
|
471
|
+
".webp": "image/webp",
|
|
472
|
+
".svg": "image/svg+xml",
|
|
473
|
+
".ico": "image/x-icon",
|
|
474
|
+
".woff": "font/woff",
|
|
475
|
+
".woff2": "font/woff2",
|
|
476
|
+
".ttf": "font/ttf",
|
|
477
|
+
".eot": "application/vnd.ms-fontobject",
|
|
478
|
+
".mp4": "video/mp4",
|
|
479
|
+
".webm": "video/webm",
|
|
480
|
+
".wav": "audio/wav",
|
|
481
|
+
".mp3": "audio/mpeg",
|
|
482
|
+
".flac": "audio/flac",
|
|
483
|
+
".aac": "audio/aac",
|
|
484
|
+
".txt": "text/plain",
|
|
485
|
+
".md": "text/markdown",
|
|
486
|
+
".html": "text/html",
|
|
487
|
+
".xml": "application/xml",
|
|
488
|
+
".css": "text/css",
|
|
489
|
+
".js": "application/javascript",
|
|
490
|
+
".json": "application/json"
|
|
491
|
+
};
|
|
492
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Create a loader plugin for robuild
|
|
496
|
+
*/
|
|
497
|
+
function createLoaderPlugin(loaders) {
|
|
498
|
+
return {
|
|
499
|
+
name: "loaders",
|
|
500
|
+
load: async (id) => {
|
|
501
|
+
const loader = getLoaderForFile(id, loaders);
|
|
502
|
+
if (loader === "js" || loader === "jsx" || loader === "ts" || loader === "tsx") return null;
|
|
503
|
+
try {
|
|
504
|
+
const content = await readFile(id, "utf-8");
|
|
505
|
+
const options = loaders?.[extname(id)]?.options;
|
|
506
|
+
return await transformWithLoader(id, content, loader, options);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/features/plugins.ts
|
|
516
|
+
/**
|
|
517
|
+
* Plugin manager for handling plugin lifecycle and execution
|
|
518
|
+
*/
|
|
519
|
+
var PluginManager = class {
|
|
520
|
+
plugins = [];
|
|
521
|
+
context;
|
|
522
|
+
constructor(config, entry) {
|
|
523
|
+
this.context = {
|
|
524
|
+
config,
|
|
525
|
+
entry,
|
|
526
|
+
plugins: config.plugins || [],
|
|
527
|
+
hooks: config.hooks || {}
|
|
528
|
+
};
|
|
529
|
+
this.plugins = config.plugins || [];
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Initialize all plugins
|
|
533
|
+
*/
|
|
534
|
+
async initialize() {
|
|
535
|
+
for (const plugin of this.plugins) if (plugin.setup) {
|
|
536
|
+
const pluginBuild = this.createPluginBuild();
|
|
537
|
+
await plugin.setup(pluginBuild);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Execute a specific hook across all plugins
|
|
542
|
+
*/
|
|
543
|
+
async executeHook(hookName, ...args) {
|
|
544
|
+
const results = [];
|
|
545
|
+
for (const plugin of this.plugins) {
|
|
546
|
+
const hook = plugin[hookName];
|
|
547
|
+
if (typeof hook === "function") try {
|
|
548
|
+
const result = await hook.apply(plugin, args);
|
|
549
|
+
results.push(result);
|
|
550
|
+
} catch (error) {
|
|
551
|
+
console.error(`Plugin ${plugin.name} hook ${hookName} failed:`, error);
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return results;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Create plugin build context for setup
|
|
559
|
+
*/
|
|
560
|
+
createPluginBuild() {
|
|
561
|
+
const resolveCallbacks = [];
|
|
562
|
+
const loadCallbacks = [];
|
|
563
|
+
const transformCallbacks = [];
|
|
564
|
+
return {
|
|
565
|
+
onResolve: (options, callback) => {
|
|
566
|
+
resolveCallbacks.push({
|
|
567
|
+
...options,
|
|
568
|
+
callback
|
|
569
|
+
});
|
|
570
|
+
},
|
|
571
|
+
onLoad: (options, callback) => {
|
|
572
|
+
loadCallbacks.push({
|
|
573
|
+
...options,
|
|
574
|
+
callback
|
|
575
|
+
});
|
|
576
|
+
},
|
|
577
|
+
onTransform: (options, callback) => {
|
|
578
|
+
transformCallbacks.push({
|
|
579
|
+
...options,
|
|
580
|
+
callback
|
|
581
|
+
});
|
|
582
|
+
},
|
|
583
|
+
resolve: async (path, options) => {
|
|
584
|
+
for (const { filter, callback } of resolveCallbacks) if (filter.test(path)) {
|
|
585
|
+
const result = await callback({
|
|
586
|
+
path,
|
|
587
|
+
...options
|
|
588
|
+
});
|
|
589
|
+
if (result) return result;
|
|
590
|
+
}
|
|
591
|
+
return null;
|
|
592
|
+
},
|
|
593
|
+
getConfig: () => this.context.config
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Get all plugins
|
|
598
|
+
*/
|
|
599
|
+
getPlugins() {
|
|
600
|
+
return this.plugins;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Add a plugin
|
|
604
|
+
*/
|
|
605
|
+
addPlugin(plugin) {
|
|
606
|
+
this.plugins.push(plugin);
|
|
607
|
+
this.context.plugins = this.plugins;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Remove a plugin by name
|
|
611
|
+
*/
|
|
612
|
+
removePlugin(name) {
|
|
613
|
+
const index = this.plugins.findIndex((p) => p.name === name);
|
|
614
|
+
if (index !== -1) {
|
|
615
|
+
this.plugins.splice(index, 1);
|
|
616
|
+
this.context.plugins = this.plugins;
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region src/features/shims.ts
|
|
625
|
+
/**
|
|
626
|
+
* Default shims configuration
|
|
627
|
+
*/
|
|
628
|
+
const DEFAULT_SHIMS_CONFIG = {
|
|
629
|
+
dirname: true,
|
|
630
|
+
require: true,
|
|
631
|
+
exports: true,
|
|
632
|
+
env: false
|
|
633
|
+
};
|
|
634
|
+
/**
|
|
635
|
+
* Node.js globals shim for ESM
|
|
636
|
+
*/
|
|
637
|
+
const NODE_GLOBALS_SHIM = `
|
|
638
|
+
// Node.js globals shim for ESM
|
|
639
|
+
import { fileURLToPath } from 'node:url'
|
|
640
|
+
import { dirname } from 'node:path'
|
|
641
|
+
import { createRequire } from 'node:module'
|
|
642
|
+
|
|
643
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
644
|
+
const __dirname = dirname(__filename)
|
|
645
|
+
const require = createRequire(import.meta.url)
|
|
646
|
+
`;
|
|
647
|
+
/**
|
|
648
|
+
* Process.env shim for browser
|
|
649
|
+
*/
|
|
650
|
+
const PROCESS_ENV_SHIM = `
|
|
651
|
+
// Process.env shim for browser
|
|
652
|
+
if (typeof process === 'undefined') {
|
|
653
|
+
globalThis.process = {
|
|
654
|
+
env: {},
|
|
655
|
+
platform: 'browser',
|
|
656
|
+
version: '0.0.0',
|
|
657
|
+
versions: { node: '0.0.0' }
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
`;
|
|
661
|
+
/**
|
|
662
|
+
* Module.exports shim for ESM
|
|
663
|
+
*/
|
|
664
|
+
const MODULE_EXPORTS_SHIM = `
|
|
665
|
+
// Module.exports shim for ESM
|
|
666
|
+
if (typeof module === 'undefined') {
|
|
667
|
+
globalThis.module = { exports: {} }
|
|
668
|
+
}
|
|
669
|
+
if (typeof exports === 'undefined') {
|
|
670
|
+
globalThis.exports = module.exports
|
|
671
|
+
}
|
|
672
|
+
`;
|
|
673
|
+
/**
|
|
674
|
+
* Detect if code needs specific shims
|
|
675
|
+
*/
|
|
676
|
+
function detectShimNeeds(code) {
|
|
677
|
+
const cleanCode = removeCommentsAndStrings(code);
|
|
678
|
+
const needsDirname = /\b__dirname\b/.test(cleanCode) || /\b__filename\b/.test(cleanCode);
|
|
679
|
+
const needsRequire = /\brequire\s*\(/.test(cleanCode);
|
|
680
|
+
const needsExports = /\bmodule\.exports\b/.test(cleanCode) || /\bexports\.\w+/.test(cleanCode);
|
|
681
|
+
const needsEnv = /\bprocess\.env\b/.test(cleanCode);
|
|
682
|
+
return {
|
|
683
|
+
needsDirname,
|
|
684
|
+
needsRequire,
|
|
685
|
+
needsExports,
|
|
686
|
+
needsEnv
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Remove comments and string literals from code
|
|
691
|
+
*/
|
|
692
|
+
function removeCommentsAndStrings(code) {
|
|
693
|
+
return code.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/"(?:[^"\\]|\\.)*"/g, "\"\"").replace(/'(?:[^'\\]|\\.)*'/g, "''").replace(/`(?:[^`\\]|\\.)*`/g, "``");
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Generate shims code based on configuration and needs
|
|
697
|
+
*/
|
|
698
|
+
function generateShims(config, needs) {
|
|
699
|
+
const shims = [];
|
|
700
|
+
if (config.dirname && needs.needsDirname) shims.push(NODE_GLOBALS_SHIM);
|
|
701
|
+
else if (config.require && needs.needsRequire) shims.push(`
|
|
702
|
+
// Require shim for ESM
|
|
703
|
+
import { createRequire } from 'node:module'
|
|
704
|
+
const require = createRequire(import.meta.url)
|
|
705
|
+
`);
|
|
706
|
+
if (config.exports && needs.needsExports) shims.push(MODULE_EXPORTS_SHIM);
|
|
707
|
+
if (config.env && needs.needsEnv) shims.push(PROCESS_ENV_SHIM);
|
|
708
|
+
return shims.join("\n");
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Transform code to use shims
|
|
712
|
+
*/
|
|
713
|
+
function transformWithShims(code, config) {
|
|
714
|
+
const needs = detectShimNeeds(code);
|
|
715
|
+
const shims = generateShims(config, needs);
|
|
716
|
+
if (!shims) return code;
|
|
717
|
+
return `${shims}\n${code}`;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Create shims plugin
|
|
721
|
+
*/
|
|
722
|
+
function createShimsPlugin(config = true) {
|
|
723
|
+
const shimsConfig = config === true ? DEFAULT_SHIMS_CONFIG : config === false ? {
|
|
724
|
+
dirname: false,
|
|
725
|
+
require: false,
|
|
726
|
+
exports: false,
|
|
727
|
+
env: false
|
|
728
|
+
} : {
|
|
729
|
+
...DEFAULT_SHIMS_CONFIG,
|
|
730
|
+
...config
|
|
731
|
+
};
|
|
732
|
+
return {
|
|
733
|
+
name: "shims",
|
|
734
|
+
transform: async (code, id) => {
|
|
735
|
+
if (!/\.(js|mjs|cjs|ts|mts|cts|jsx|tsx)$/.test(id)) return null;
|
|
736
|
+
const needs = detectShimNeeds(code);
|
|
737
|
+
if (!needs.needsDirname && !needs.needsRequire && !needs.needsExports && !needs.needsEnv) return null;
|
|
738
|
+
const transformedCode = transformWithShims(code, shimsConfig);
|
|
739
|
+
return transformedCode !== code ? transformedCode : null;
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
//#endregion
|
|
745
|
+
//#region src/utils.ts
|
|
746
|
+
function fmtPath(path) {
|
|
747
|
+
return resolve(path).replace(process.cwd(), ".");
|
|
748
|
+
}
|
|
749
|
+
function analyzeDir(dir) {
|
|
750
|
+
if (Array.isArray(dir)) {
|
|
751
|
+
let totalSize$1 = 0;
|
|
752
|
+
let totalFiles = 0;
|
|
753
|
+
for (const d of dir) {
|
|
754
|
+
const { size, files: files$1 } = analyzeDir(d);
|
|
755
|
+
totalSize$1 += size;
|
|
756
|
+
totalFiles += files$1;
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
size: totalSize$1,
|
|
760
|
+
files: totalFiles
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
let totalSize = 0;
|
|
764
|
+
const files = readdirSync(dir, {
|
|
765
|
+
withFileTypes: true,
|
|
766
|
+
recursive: true
|
|
767
|
+
});
|
|
768
|
+
for (const file of files) {
|
|
769
|
+
const fullPath = join(file.parentPath, file.name);
|
|
770
|
+
if (file.isFile()) {
|
|
771
|
+
const { size } = statSync(fullPath);
|
|
772
|
+
totalSize += size;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
size: totalSize,
|
|
777
|
+
files: files.length
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
async function distSize(dir, entry) {
|
|
781
|
+
const build$1 = await rolldown({
|
|
782
|
+
input: join(dir, entry),
|
|
783
|
+
plugins: [],
|
|
784
|
+
platform: "neutral",
|
|
785
|
+
external: (id) => id[0] !== "." && !id.startsWith(dir)
|
|
786
|
+
});
|
|
787
|
+
const { output } = await build$1.generate({ inlineDynamicImports: true });
|
|
788
|
+
const code = output[0].code;
|
|
789
|
+
const { code: minified } = await minify(entry, code);
|
|
790
|
+
return {
|
|
791
|
+
size: Buffer.byteLength(code),
|
|
792
|
+
minSize: Buffer.byteLength(minified),
|
|
793
|
+
minGzipSize: gzipSync(minified).length
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
async function sideEffectSize(dir, entry) {
|
|
797
|
+
const virtualEntry = {
|
|
798
|
+
name: "virtual-entry",
|
|
799
|
+
async resolveId(id, importer, opts) {
|
|
800
|
+
if (id === "#entry") return { id };
|
|
801
|
+
const resolved = await this.resolve(id, importer, opts);
|
|
802
|
+
if (!resolved) return null;
|
|
803
|
+
resolved.moduleSideEffects = null;
|
|
804
|
+
return resolved;
|
|
805
|
+
},
|
|
806
|
+
load(id) {
|
|
807
|
+
if (id === "#entry") return `import * as _lib from "${join(dir, entry)}";`;
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
const build$1 = await rolldown({
|
|
811
|
+
input: "#entry",
|
|
812
|
+
platform: "neutral",
|
|
813
|
+
external: (id) => id[0] !== "." && !id.startsWith(dir),
|
|
814
|
+
plugins: [virtualEntry]
|
|
815
|
+
});
|
|
816
|
+
const { output } = await build$1.generate({ inlineDynamicImports: true });
|
|
817
|
+
if (process.env.INSPECT_BUILD) {
|
|
818
|
+
console.log("---------[side effects]---------");
|
|
819
|
+
console.log(entry);
|
|
820
|
+
console.log(output[0].code);
|
|
821
|
+
console.log("-------------------------------");
|
|
822
|
+
}
|
|
823
|
+
return Buffer.byteLength(output[0].code.trim());
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
//#endregion
|
|
827
|
+
//#region src/features/node-protocol.ts
|
|
828
|
+
/**
|
|
829
|
+
* Node.js built-in modules that support the node: protocol
|
|
830
|
+
*/
|
|
831
|
+
const NODE_BUILTIN_MODULES = new Set([
|
|
832
|
+
"assert",
|
|
833
|
+
"async_hooks",
|
|
834
|
+
"buffer",
|
|
835
|
+
"child_process",
|
|
836
|
+
"cluster",
|
|
837
|
+
"console",
|
|
838
|
+
"constants",
|
|
839
|
+
"crypto",
|
|
840
|
+
"dgram",
|
|
841
|
+
"diagnostics_channel",
|
|
842
|
+
"dns",
|
|
843
|
+
"domain",
|
|
844
|
+
"events",
|
|
845
|
+
"fs",
|
|
846
|
+
"http",
|
|
847
|
+
"http2",
|
|
848
|
+
"https",
|
|
849
|
+
"inspector",
|
|
850
|
+
"module",
|
|
851
|
+
"net",
|
|
852
|
+
"os",
|
|
853
|
+
"path",
|
|
854
|
+
"perf_hooks",
|
|
855
|
+
"process",
|
|
856
|
+
"punycode",
|
|
857
|
+
"querystring",
|
|
858
|
+
"readline",
|
|
859
|
+
"repl",
|
|
860
|
+
"stream",
|
|
861
|
+
"string_decoder",
|
|
862
|
+
"sys",
|
|
863
|
+
"timers",
|
|
864
|
+
"tls",
|
|
865
|
+
"trace_events",
|
|
866
|
+
"tty",
|
|
867
|
+
"url",
|
|
868
|
+
"util",
|
|
869
|
+
"v8",
|
|
870
|
+
"vm",
|
|
871
|
+
"wasi",
|
|
872
|
+
"worker_threads",
|
|
873
|
+
"zlib"
|
|
874
|
+
]);
|
|
875
|
+
/**
|
|
876
|
+
* Check if a module is a Node.js built-in module
|
|
877
|
+
*/
|
|
878
|
+
function isNodeBuiltin(id) {
|
|
879
|
+
const cleanId = id.startsWith("node:") ? id.slice(5) : id;
|
|
880
|
+
return NODE_BUILTIN_MODULES.has(cleanId);
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Add node: prefix to built-in modules
|
|
884
|
+
*/
|
|
885
|
+
function addNodeProtocol(id) {
|
|
886
|
+
if (id.startsWith("node:")) return id;
|
|
887
|
+
if (isNodeBuiltin(id)) return `node:${id}`;
|
|
888
|
+
return id;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Remove node: prefix from modules
|
|
892
|
+
*/
|
|
893
|
+
function stripNodeProtocol(id) {
|
|
894
|
+
if (id.startsWith("node:")) return id.slice(5);
|
|
895
|
+
return id;
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Process module ID based on nodeProtocol setting
|
|
899
|
+
*/
|
|
900
|
+
function processNodeProtocol(id, nodeProtocol) {
|
|
901
|
+
if (nodeProtocol === "strip") return stripNodeProtocol(id);
|
|
902
|
+
if (nodeProtocol === true) return addNodeProtocol(id);
|
|
903
|
+
return id;
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Transform import/export statements to handle node protocol
|
|
907
|
+
*/
|
|
908
|
+
function transformNodeProtocol(code, nodeProtocol) {
|
|
909
|
+
if (!nodeProtocol) return code;
|
|
910
|
+
const importRegex = /(?:import|export)(?:\s[^'"]*)?\s['"]([^'"]+)['"]/g;
|
|
911
|
+
return code.replace(importRegex, (match, moduleId) => {
|
|
912
|
+
const processedId = processNodeProtocol(moduleId, nodeProtocol);
|
|
913
|
+
return match.replace(moduleId, processedId);
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
//#endregion
|
|
918
|
+
//#region src/builders/plugins/node-protocol.ts
|
|
919
|
+
/**
|
|
920
|
+
* Rolldown plugin for Node.js protocol handling
|
|
921
|
+
*/
|
|
922
|
+
function nodeProtocolPlugin(nodeProtocol) {
|
|
923
|
+
if (!nodeProtocol) return { name: "node-protocol-noop" };
|
|
924
|
+
return {
|
|
925
|
+
name: "node-protocol",
|
|
926
|
+
renderChunk(code) {
|
|
927
|
+
return {
|
|
928
|
+
code: transformNodeProtocol(code, nodeProtocol),
|
|
929
|
+
map: null
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region src/builders/plugins/shebang.ts
|
|
937
|
+
const SHEBANG_RE = /^#![^\n]*/;
|
|
938
|
+
function shebangPlugin() {
|
|
939
|
+
return {
|
|
940
|
+
name: "robuild-shebang",
|
|
941
|
+
async writeBundle(options, bundle) {
|
|
942
|
+
for (const [fileName, output] of Object.entries(bundle)) {
|
|
943
|
+
if (output.type !== "chunk") continue;
|
|
944
|
+
if (hasShebang(output.code)) {
|
|
945
|
+
const outFile = resolve(options.dir, fileName);
|
|
946
|
+
await makeExecutable(outFile);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
function hasShebang(code) {
|
|
953
|
+
return SHEBANG_RE.test(code);
|
|
954
|
+
}
|
|
955
|
+
async function makeExecutable(filePath) {
|
|
956
|
+
await promises.chmod(filePath, 493).catch(() => {});
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
//#endregion
|
|
960
|
+
//#region src/builders/bundle.ts
|
|
961
|
+
/**
|
|
962
|
+
* Convert OutputFormat to Rolldown ModuleFormat
|
|
963
|
+
*/
|
|
964
|
+
function formatToRolldownFormat(format) {
|
|
965
|
+
switch (format) {
|
|
966
|
+
case "esm": return "es";
|
|
967
|
+
case "cjs": return "cjs";
|
|
968
|
+
case "iife": return "iife";
|
|
969
|
+
case "umd": return "umd";
|
|
970
|
+
default: return "es";
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Get file extension for format
|
|
975
|
+
*/
|
|
976
|
+
function getFormatExtension(format, platform, fixedExtension = false) {
|
|
977
|
+
if (fixedExtension) return format === "cjs" ? ".cjs" : ".mjs";
|
|
978
|
+
switch (format) {
|
|
979
|
+
case "esm": return ".mjs";
|
|
980
|
+
case "cjs": return platform === "node" ? ".cjs" : ".js";
|
|
981
|
+
case "iife":
|
|
982
|
+
case "umd": return ".js";
|
|
983
|
+
default: return ".js";
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Clean output directory
|
|
988
|
+
*/
|
|
989
|
+
async function cleanOutputDir$1(projectRoot, outDir, cleanPaths) {
|
|
990
|
+
if (!cleanPaths) return;
|
|
991
|
+
const { rm } = await import("node:fs/promises");
|
|
992
|
+
const { existsSync: existsSync$1 } = await import("node:fs");
|
|
993
|
+
if (cleanPaths === true) {
|
|
994
|
+
if (existsSync$1(outDir)) {
|
|
995
|
+
consola.log(`š§» Cleaning up ${fmtPath(outDir)}`);
|
|
996
|
+
await rm(outDir, {
|
|
997
|
+
recursive: true,
|
|
998
|
+
force: true
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
} else if (Array.isArray(cleanPaths)) for (const path of cleanPaths) {
|
|
1002
|
+
const fullPath = resolve(projectRoot, path);
|
|
1003
|
+
if (existsSync$1(fullPath)) {
|
|
1004
|
+
consola.log(`š§» Cleaning up ${fmtPath(fullPath)}`);
|
|
1005
|
+
await rm(fullPath, {
|
|
1006
|
+
recursive: true,
|
|
1007
|
+
force: true
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
async function rolldownBuild(ctx, entry, hooks, config) {
|
|
1013
|
+
const inputs = normalizeBundleInputs(entry.input, ctx);
|
|
1014
|
+
const pluginManager = new PluginManager(config || {}, entry);
|
|
1015
|
+
await pluginManager.initialize();
|
|
1016
|
+
const formats = Array.isArray(entry.format) ? entry.format : [entry.format || "esm"];
|
|
1017
|
+
const platform = entry.platform || "node";
|
|
1018
|
+
const target = entry.target || "es2022";
|
|
1019
|
+
const outDir = entry.outDir || "dist";
|
|
1020
|
+
const fullOutDir = resolve(ctx.pkgDir, outDir);
|
|
1021
|
+
const isMultiFormat = formats.length > 1;
|
|
1022
|
+
await pluginManager.executeHook("buildStart", {
|
|
1023
|
+
inputs,
|
|
1024
|
+
formats,
|
|
1025
|
+
platform,
|
|
1026
|
+
target
|
|
1027
|
+
});
|
|
1028
|
+
await cleanOutputDir$1(ctx.pkgDir, fullOutDir, entry.clean ?? true);
|
|
1029
|
+
if (entry.stub) {
|
|
1030
|
+
for (const [distName, srcPath] of Object.entries(inputs)) {
|
|
1031
|
+
const distPath = join(ctx.pkgDir, "dist", `${distName}.mjs`);
|
|
1032
|
+
await mkdir(dirname(distPath), { recursive: true });
|
|
1033
|
+
consola.log(`${colors.magenta("[stub bundle] ")} ${colors.underline(fmtPath(distPath))}`);
|
|
1034
|
+
const srcContents = await readFile(srcPath, "utf8");
|
|
1035
|
+
const parsed = parseSync(srcPath, srcContents);
|
|
1036
|
+
const exportNames = parsed.module.staticExports.flatMap((e) => e.entries.map((e$1) => e$1.exportName.kind === "Default" ? "default" : e$1.exportName.name));
|
|
1037
|
+
const hasDefaultExport = exportNames.includes("default");
|
|
1038
|
+
const firstLine = srcContents.split("\n")[0];
|
|
1039
|
+
const hasShebangLine = firstLine.startsWith("#!");
|
|
1040
|
+
await writeFile(distPath, `${hasShebangLine ? `${firstLine}\n` : ""}export * from "${srcPath}";\n${hasDefaultExport ? `export { default } from "${srcPath}";\n` : ""}`, "utf8");
|
|
1041
|
+
if (hasShebangLine) await makeExecutable(distPath);
|
|
1042
|
+
await writeFile(distPath.replace(/\.mjs$/, ".d.mts"), `export * from "${srcPath}";\n${hasDefaultExport ? `export { default } from "${srcPath}";\n` : ""}`, "utf8");
|
|
1043
|
+
}
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const externalDeps = [
|
|
1047
|
+
...builtinModules,
|
|
1048
|
+
...builtinModules.map((m) => `node:${m}`),
|
|
1049
|
+
...[...Object.keys(ctx.pkg.dependencies || {}), ...Object.keys(ctx.pkg.peerDependencies || {})].flatMap((p) => [p, /* @__PURE__ */ new RegExp(`^${p}/`)])
|
|
1050
|
+
];
|
|
1051
|
+
if (entry.external) if (typeof entry.external === "function") {} else externalDeps.push(...entry.external);
|
|
1052
|
+
const defineOptions = {};
|
|
1053
|
+
if (entry.env) for (const [key, value] of Object.entries(entry.env)) defineOptions[`process.env.${key}`] = JSON.stringify(value);
|
|
1054
|
+
if (entry.define) for (const [key, value] of Object.entries(entry.define)) defineOptions[key] = value;
|
|
1055
|
+
const rolldownPlugins = [];
|
|
1056
|
+
rolldownPlugins.push(shebangPlugin(), nodeProtocolPlugin(entry.nodeProtocol || false));
|
|
1057
|
+
if (config?.globImport?.enabled) {
|
|
1058
|
+
const globPlugin = createGlobImportPlugin(config.globImport);
|
|
1059
|
+
if (globPlugin.transform) rolldownPlugins.push({
|
|
1060
|
+
name: "glob-import",
|
|
1061
|
+
transform: async (code, id) => {
|
|
1062
|
+
const result = await globPlugin.transform(code, id);
|
|
1063
|
+
return result ? { code: result } : null;
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
if (entry.loaders) {
|
|
1068
|
+
const loaderPlugin = createLoaderPlugin(entry.loaders);
|
|
1069
|
+
if (loaderPlugin.load) rolldownPlugins.push({
|
|
1070
|
+
name: "loaders",
|
|
1071
|
+
load: loaderPlugin.load
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
if (entry.shims) {
|
|
1075
|
+
const shimsPlugin = createShimsPlugin(entry.shims);
|
|
1076
|
+
if (shimsPlugin.transform) rolldownPlugins.push({
|
|
1077
|
+
name: "shims",
|
|
1078
|
+
transform: async (code, id) => {
|
|
1079
|
+
const result = await shimsPlugin.transform(code, id);
|
|
1080
|
+
return result ? { code: result } : null;
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
if (entry.skipNodeModules) {
|
|
1085
|
+
const skipPlugin = createSkipNodeModulesPlugin();
|
|
1086
|
+
if (skipPlugin.resolveId) rolldownPlugins.push({
|
|
1087
|
+
name: "skip-node-modules",
|
|
1088
|
+
resolveId: skipPlugin.resolveId
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
if (config?.plugins) {
|
|
1092
|
+
for (const plugin of pluginManager.getPlugins()) if (plugin.transform || plugin.resolveId || plugin.load) rolldownPlugins.push({
|
|
1093
|
+
name: plugin.name,
|
|
1094
|
+
resolveId: plugin.resolveId,
|
|
1095
|
+
load: plugin.load,
|
|
1096
|
+
transform: plugin.transform ? async (code, id) => {
|
|
1097
|
+
const result = await plugin.transform(code, id);
|
|
1098
|
+
return result ? typeof result === "string" ? { code: result } : result : null;
|
|
1099
|
+
} : void 0
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
const baseRolldownConfig = defu(entry.rolldown, {
|
|
1103
|
+
cwd: ctx.pkgDir,
|
|
1104
|
+
input: inputs,
|
|
1105
|
+
plugins: rolldownPlugins,
|
|
1106
|
+
platform: platform === "node" ? "node" : "neutral",
|
|
1107
|
+
external: typeof entry.external === "function" ? entry.external : externalDeps,
|
|
1108
|
+
define: defineOptions,
|
|
1109
|
+
resolve: { alias: entry.alias || {} },
|
|
1110
|
+
transform: { target }
|
|
1111
|
+
});
|
|
1112
|
+
await hooks.rolldownConfig?.(baseRolldownConfig, ctx);
|
|
1113
|
+
const allOutputEntries = [];
|
|
1114
|
+
const filePathMap = /* @__PURE__ */ new Map();
|
|
1115
|
+
for (const format of formats) {
|
|
1116
|
+
const rolldownFormat = formatToRolldownFormat(format);
|
|
1117
|
+
const extension = getFormatExtension(format, platform, entry.fixedExtension);
|
|
1118
|
+
const formatConfig = { ...baseRolldownConfig };
|
|
1119
|
+
if (entry.dts !== false && format === "esm") formatConfig.plugins = [...formatConfig.plugins, ...dts({ ...entry.dts })];
|
|
1120
|
+
const res = await rolldown(formatConfig);
|
|
1121
|
+
let formatOutDir = fullOutDir;
|
|
1122
|
+
let entryFileName = `[name]${extension}`;
|
|
1123
|
+
if (isMultiFormat) {
|
|
1124
|
+
if (format === "cjs") {
|
|
1125
|
+
formatOutDir = join(fullOutDir, "cjs");
|
|
1126
|
+
entryFileName = `[name].cjs`;
|
|
1127
|
+
} else if (format === "iife" || format === "umd") {
|
|
1128
|
+
formatOutDir = join(fullOutDir, platform === "browser" ? "browser" : format);
|
|
1129
|
+
entryFileName = `[name].js`;
|
|
1130
|
+
}
|
|
1131
|
+
} else if ((format === "iife" || format === "umd") && platform === "browser") {
|
|
1132
|
+
formatOutDir = join(fullOutDir, "browser");
|
|
1133
|
+
entryFileName = `[name].js`;
|
|
1134
|
+
}
|
|
1135
|
+
const outConfig = {
|
|
1136
|
+
dir: formatOutDir,
|
|
1137
|
+
format: rolldownFormat,
|
|
1138
|
+
entryFileNames: entryFileName,
|
|
1139
|
+
chunkFileNames: `_chunks/[name]-[hash]${extension}`,
|
|
1140
|
+
minify: entry.minify,
|
|
1141
|
+
name: entry.globalName,
|
|
1142
|
+
banner: resolveChunkAddon(entry.banner, format),
|
|
1143
|
+
footer: resolveChunkAddon(entry.footer, format)
|
|
1144
|
+
};
|
|
1145
|
+
await hooks.rolldownOutput?.(outConfig, res, ctx);
|
|
1146
|
+
const { output } = await res.write(outConfig);
|
|
1147
|
+
await res.close();
|
|
1148
|
+
const depsCache = /* @__PURE__ */ new Map();
|
|
1149
|
+
const resolveDeps = (chunk) => {
|
|
1150
|
+
if (!depsCache.has(chunk)) depsCache.set(chunk, /* @__PURE__ */ new Set());
|
|
1151
|
+
const deps = depsCache.get(chunk);
|
|
1152
|
+
for (const id of chunk.imports) {
|
|
1153
|
+
if (builtinModules.includes(id) || id.startsWith("node:")) {
|
|
1154
|
+
deps.add(`[Node.js]`);
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
const depChunk = output.find((o) => o.type === "chunk" && o.fileName === id);
|
|
1158
|
+
if (depChunk) {
|
|
1159
|
+
for (const dep of resolveDeps(depChunk)) deps.add(dep);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
deps.add(id);
|
|
1163
|
+
}
|
|
1164
|
+
return Array.from(deps).sort();
|
|
1165
|
+
};
|
|
1166
|
+
for (const chunk of output) {
|
|
1167
|
+
if (chunk.type !== "chunk" || !chunk.isEntry) continue;
|
|
1168
|
+
if (chunk.fileName.endsWith("ts")) continue;
|
|
1169
|
+
let finalFileName = chunk.fileName;
|
|
1170
|
+
let finalFilePath = join(formatOutDir, chunk.fileName);
|
|
1171
|
+
if (entry.hash && !hasHash(chunk.fileName)) {
|
|
1172
|
+
const content = chunk.code;
|
|
1173
|
+
const hashedFileName = addHashToFilename(chunk.fileName, content);
|
|
1174
|
+
const hashedFilePath = join(formatOutDir, hashedFileName);
|
|
1175
|
+
const { rename } = await import("node:fs/promises");
|
|
1176
|
+
await rename(finalFilePath, hashedFilePath);
|
|
1177
|
+
finalFileName = hashedFileName;
|
|
1178
|
+
finalFilePath = hashedFilePath;
|
|
1179
|
+
}
|
|
1180
|
+
filePathMap.set(finalFileName, finalFilePath);
|
|
1181
|
+
allOutputEntries.push({
|
|
1182
|
+
format,
|
|
1183
|
+
name: finalFileName,
|
|
1184
|
+
exports: chunk.exports,
|
|
1185
|
+
deps: resolveDeps(chunk),
|
|
1186
|
+
...await distSize(formatOutDir, finalFileName),
|
|
1187
|
+
sideEffectSize: await sideEffectSize(formatOutDir, finalFileName)
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (entry.copy) await copyFiles(ctx.pkgDir, fullOutDir, entry.copy);
|
|
1192
|
+
await pluginManager.executeHook("buildEnd", { allOutputEntries });
|
|
1193
|
+
consola.log(`\n${allOutputEntries.map((o) => [
|
|
1194
|
+
`${colors.magenta(`[bundle] `)}${colors.underline(fmtPath(filePathMap.get(o.name) || join(fullOutDir, o.name)))}`,
|
|
1195
|
+
colors.dim(`${colors.bold("Size:")} ${prettyBytes(o.size)}, ${colors.bold(prettyBytes(o.minSize))} minified, ${prettyBytes(o.minGzipSize)} min+gzipped (Side effects: ${prettyBytes(o.sideEffectSize)})`),
|
|
1196
|
+
o.exports.some((e) => e !== "default") ? colors.dim(`${colors.bold("Exports:")} ${o.exports.map((e) => e).join(", ")}`) : "",
|
|
1197
|
+
o.deps.length > 0 ? colors.dim(`${colors.bold("Dependencies:")} ${o.deps.join(", ")}`) : ""
|
|
1198
|
+
].filter(Boolean).join("\n")).join("\n\n")}`);
|
|
1199
|
+
}
|
|
1200
|
+
function normalizeBundleInputs(input, ctx) {
|
|
1201
|
+
const inputs = {};
|
|
1202
|
+
for (let src of Array.isArray(input) ? input : [input]) {
|
|
1203
|
+
src = resolveModulePath(src, {
|
|
1204
|
+
from: ctx.pkgDir,
|
|
1205
|
+
extensions: [
|
|
1206
|
+
".ts",
|
|
1207
|
+
".js",
|
|
1208
|
+
".mjs",
|
|
1209
|
+
".cjs",
|
|
1210
|
+
".json"
|
|
1211
|
+
]
|
|
1212
|
+
});
|
|
1213
|
+
let relativeSrc = relative(join(ctx.pkgDir, "src"), src);
|
|
1214
|
+
if (relativeSrc.startsWith("..")) relativeSrc = relative(join(ctx.pkgDir), src);
|
|
1215
|
+
if (relativeSrc.startsWith("..")) throw new Error(`Source should be within the package directory (${ctx.pkgDir}): ${src}`);
|
|
1216
|
+
const distName = join(dirname(relativeSrc), basename(relativeSrc, extname(relativeSrc)));
|
|
1217
|
+
if (inputs[distName]) throw new Error(`Rename one of the entries to avoid a conflict in the dist name "${distName}":\n - ${src}\n - ${inputs[distName]}`);
|
|
1218
|
+
inputs[distName] = src;
|
|
1219
|
+
}
|
|
1220
|
+
return inputs;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
//#endregion
|
|
1224
|
+
//#region src/features/extensions.ts
|
|
1225
|
+
/**
|
|
1226
|
+
* Resolve JavaScript output extension
|
|
1227
|
+
*/
|
|
1228
|
+
function resolveJsOutputExtension(format, platform = "node", fixedExtension = false) {
|
|
1229
|
+
if (fixedExtension) return format === "cjs" ? "cjs" : "mjs";
|
|
1230
|
+
switch (format) {
|
|
1231
|
+
case "esm": return platform === "browser" ? "js" : "mjs";
|
|
1232
|
+
case "cjs": return platform === "browser" ? "js" : "cjs";
|
|
1233
|
+
case "iife":
|
|
1234
|
+
case "umd": return "js";
|
|
1235
|
+
default: return "js";
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Resolve DTS output extension
|
|
1240
|
+
*/
|
|
1241
|
+
function resolveDtsOutputExtension(format, fixedExtension = false) {
|
|
1242
|
+
if (fixedExtension) return format === "cjs" ? "d.cts" : "d.mts";
|
|
1243
|
+
switch (format) {
|
|
1244
|
+
case "esm": return "d.mts";
|
|
1245
|
+
case "cjs": return "d.cts";
|
|
1246
|
+
default: return "d.ts";
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Apply custom output extensions
|
|
1251
|
+
*/
|
|
1252
|
+
function applyOutExtensions(format, outExtensions) {
|
|
1253
|
+
const defaultJs = resolveJsOutputExtension(format);
|
|
1254
|
+
const defaultDts = resolveDtsOutputExtension(format);
|
|
1255
|
+
if (!outExtensions) return {
|
|
1256
|
+
js: defaultJs,
|
|
1257
|
+
dts: defaultDts
|
|
1258
|
+
};
|
|
1259
|
+
const custom = outExtensions(format);
|
|
1260
|
+
return {
|
|
1261
|
+
js: custom.js || defaultJs,
|
|
1262
|
+
dts: custom.dts || defaultDts
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Create filename with proper extension
|
|
1267
|
+
*/
|
|
1268
|
+
function createFilename(basename$1, format, isDts = false, options = {}) {
|
|
1269
|
+
const { platform, fixedExtension, outExtensions } = options;
|
|
1270
|
+
if (outExtensions) {
|
|
1271
|
+
const extensions = applyOutExtensions(format, outExtensions);
|
|
1272
|
+
return `${basename$1}.${isDts ? extensions.dts : extensions.js}`;
|
|
1273
|
+
}
|
|
1274
|
+
if (isDts) return `${basename$1}.${resolveDtsOutputExtension(format, fixedExtension)}`;
|
|
1275
|
+
return `${basename$1}.${resolveJsOutputExtension(format, platform, fixedExtension)}`;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
//#endregion
|
|
1279
|
+
//#region src/builders/transform.ts
|
|
1280
|
+
/**
|
|
1281
|
+
* Clean output directory for transform entries
|
|
1282
|
+
*/
|
|
1283
|
+
async function cleanOutputDir(projectRoot, outDir, cleanPaths) {
|
|
1284
|
+
if (!cleanPaths) return;
|
|
1285
|
+
const { rm } = await import("node:fs/promises");
|
|
1286
|
+
const { existsSync: existsSync$1 } = await import("node:fs");
|
|
1287
|
+
if (cleanPaths === true) {
|
|
1288
|
+
if (existsSync$1(outDir)) {
|
|
1289
|
+
consola.log(`š§» Cleaning up ${fmtPath(outDir)}`);
|
|
1290
|
+
await rm(outDir, {
|
|
1291
|
+
recursive: true,
|
|
1292
|
+
force: true
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
} else if (Array.isArray(cleanPaths)) for (const path of cleanPaths) {
|
|
1296
|
+
const fullPath = resolve(projectRoot, path);
|
|
1297
|
+
if (existsSync$1(fullPath)) {
|
|
1298
|
+
consola.log(`š§» Cleaning up ${fmtPath(fullPath)}`);
|
|
1299
|
+
await rm(fullPath, {
|
|
1300
|
+
recursive: true,
|
|
1301
|
+
force: true
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Transform all .ts modules in a directory using oxc-transform.
|
|
1308
|
+
*/
|
|
1309
|
+
async function transformDir(ctx, entry) {
|
|
1310
|
+
if (entry.stub) {
|
|
1311
|
+
consola.log(`${colors.magenta("[stub transform] ")} ${colors.underline(`${fmtPath(entry.outDir)}/`)}`);
|
|
1312
|
+
await symlink(entry.input, entry.outDir, "junction");
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (entry.unbundle) {
|
|
1316
|
+
consola.log(`${colors.magenta("[unbundle] ")} ${colors.underline(`${fmtPath(entry.outDir)}/`)}`);
|
|
1317
|
+
await unbundleTransform(ctx, entry);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
const fullOutDir = resolve(ctx.pkgDir, entry.outDir);
|
|
1321
|
+
await cleanOutputDir(ctx.pkgDir, fullOutDir, entry.clean ?? true);
|
|
1322
|
+
const promises$1 = [];
|
|
1323
|
+
for await (const entryName of await glob$1("**/*.*", { cwd: entry.input })) promises$1.push((async () => {
|
|
1324
|
+
const entryPath = join(entry.input, entryName);
|
|
1325
|
+
const ext = extname(entryPath);
|
|
1326
|
+
switch (ext) {
|
|
1327
|
+
case ".ts": {
|
|
1328
|
+
const transformed = await transformModule(entryPath, entry);
|
|
1329
|
+
const baseName = entryName.replace(/\.ts$/, "");
|
|
1330
|
+
const outputFileName = createFilename(baseName, "esm", false, {
|
|
1331
|
+
platform: entry.platform,
|
|
1332
|
+
fixedExtension: entry.fixedExtension,
|
|
1333
|
+
outExtensions: entry.outExtensions
|
|
1334
|
+
});
|
|
1335
|
+
let entryDistPath = join(entry.outDir, outputFileName);
|
|
1336
|
+
await mkdir(dirname(entryDistPath), { recursive: true });
|
|
1337
|
+
await writeFile(entryDistPath, transformed.code, "utf8");
|
|
1338
|
+
if (entry.hash && !hasHash(entryDistPath)) {
|
|
1339
|
+
const hashedPath = addHashToFilename(entryDistPath, transformed.code);
|
|
1340
|
+
const { rename } = await import("node:fs/promises");
|
|
1341
|
+
await rename(entryDistPath, hashedPath);
|
|
1342
|
+
entryDistPath = hashedPath;
|
|
1343
|
+
}
|
|
1344
|
+
if (SHEBANG_RE.test(transformed.code)) await makeExecutable(entryDistPath);
|
|
1345
|
+
if (transformed.declaration) {
|
|
1346
|
+
const dtsFileName = createFilename(baseName, "esm", true, {
|
|
1347
|
+
platform: entry.platform,
|
|
1348
|
+
fixedExtension: entry.fixedExtension,
|
|
1349
|
+
outExtensions: entry.outExtensions
|
|
1350
|
+
});
|
|
1351
|
+
const dtsPath = join(entry.outDir, dtsFileName);
|
|
1352
|
+
await writeFile(dtsPath, transformed.declaration, "utf8");
|
|
1353
|
+
}
|
|
1354
|
+
return entryDistPath;
|
|
1355
|
+
}
|
|
1356
|
+
default: {
|
|
1357
|
+
const entryDistPath = join(entry.outDir, entryName);
|
|
1358
|
+
await mkdir(dirname(entryDistPath), { recursive: true });
|
|
1359
|
+
const code = await readFile(entryPath, "utf8");
|
|
1360
|
+
await writeFile(entryDistPath, code, "utf8");
|
|
1361
|
+
if (SHEBANG_RE.test(code)) await makeExecutable(entryDistPath);
|
|
1362
|
+
return entryDistPath;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
})());
|
|
1366
|
+
const writtenFiles = await Promise.all(promises$1);
|
|
1367
|
+
if (entry.copy) await copyFiles(ctx.pkgDir, fullOutDir, entry.copy);
|
|
1368
|
+
consola.log(`\n${colors.magenta("[transform] ")}${colors.underline(`${fmtPath(entry.outDir)}/`)}\n${writtenFiles.map((f) => colors.dim(fmtPath(f))).join("\n\n")}`);
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Transform a .ts module using oxc-transform.
|
|
1372
|
+
*/
|
|
1373
|
+
async function transformModule(entryPath, entry) {
|
|
1374
|
+
let sourceText = await readFile(entryPath, "utf8");
|
|
1375
|
+
const sourceOptions = {
|
|
1376
|
+
lang: "ts",
|
|
1377
|
+
sourceType: "module"
|
|
1378
|
+
};
|
|
1379
|
+
const parsed = parseSync(entryPath, sourceText, { ...sourceOptions });
|
|
1380
|
+
if (parsed.errors.length > 0) {
|
|
1381
|
+
const error = /* @__PURE__ */ new Error(`Errors while parsing ${entryPath}:`);
|
|
1382
|
+
error.cause = parsed.errors;
|
|
1383
|
+
throw error;
|
|
1384
|
+
}
|
|
1385
|
+
const resolveOptions = {
|
|
1386
|
+
...entry.resolve,
|
|
1387
|
+
from: pathToFileURL(entryPath),
|
|
1388
|
+
extensions: entry.resolve?.extensions ?? [
|
|
1389
|
+
".ts",
|
|
1390
|
+
".js",
|
|
1391
|
+
".mjs",
|
|
1392
|
+
".cjs",
|
|
1393
|
+
".json"
|
|
1394
|
+
],
|
|
1395
|
+
suffixes: entry.resolve?.suffixes ?? ["", "/index"]
|
|
1396
|
+
};
|
|
1397
|
+
const magicString = new MagicString(sourceText);
|
|
1398
|
+
const updatedStarts = /* @__PURE__ */ new Set();
|
|
1399
|
+
const rewriteSpecifier = (req) => {
|
|
1400
|
+
let moduleId = req.value;
|
|
1401
|
+
let wasAliasResolved = false;
|
|
1402
|
+
if (entry.alias) {
|
|
1403
|
+
for (const [alias, target] of Object.entries(entry.alias)) if (moduleId === alias || moduleId.startsWith(`${alias}/`)) {
|
|
1404
|
+
moduleId = moduleId.replace(alias, target);
|
|
1405
|
+
wasAliasResolved = true;
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (!moduleId.startsWith(".") && !wasAliasResolved) return;
|
|
1410
|
+
if (updatedStarts.has(req.start)) return;
|
|
1411
|
+
updatedStarts.add(req.start);
|
|
1412
|
+
const resolvedAbsolute = resolveModulePath(moduleId, resolveOptions);
|
|
1413
|
+
const newId = relative(dirname(entryPath), resolvedAbsolute.replace(/\.ts$/, ".mjs"));
|
|
1414
|
+
magicString.remove(req.start, req.end);
|
|
1415
|
+
magicString.prependLeft(req.start, JSON.stringify(newId.startsWith(".") ? newId : `./${newId}`));
|
|
1416
|
+
};
|
|
1417
|
+
for (const staticImport of parsed.module.staticImports) rewriteSpecifier(staticImport.moduleRequest);
|
|
1418
|
+
for (const staticExport of parsed.module.staticExports) for (const staticExportEntry of staticExport.entries) if (staticExportEntry.moduleRequest) rewriteSpecifier(staticExportEntry.moduleRequest);
|
|
1419
|
+
sourceText = magicString.toString();
|
|
1420
|
+
const transformed = transform(entryPath, sourceText, {
|
|
1421
|
+
...entry.oxc,
|
|
1422
|
+
...sourceOptions,
|
|
1423
|
+
cwd: dirname(entryPath),
|
|
1424
|
+
target: entry.target || "es2022",
|
|
1425
|
+
typescript: {
|
|
1426
|
+
declaration: { stripInternal: true },
|
|
1427
|
+
...entry.oxc?.typescript
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
const transformErrors = transformed.errors.filter((err) => !err.message.includes("--isolatedDeclarations"));
|
|
1431
|
+
if (transformErrors.length > 0) {
|
|
1432
|
+
await writeFile("build-dump.ts", `/** Error dump for ${entryPath} */\n\n${sourceText}`, "utf8");
|
|
1433
|
+
const error = /* @__PURE__ */ new Error(`Errors while transforming ${entryPath}: (hint: check build-dump.ts)`);
|
|
1434
|
+
error.cause = transformErrors;
|
|
1435
|
+
throw error;
|
|
1436
|
+
}
|
|
1437
|
+
if (entry.minify) {
|
|
1438
|
+
const res = minify(entryPath, transformed.code, entry.minify === true ? {} : entry.minify);
|
|
1439
|
+
transformed.code = res.code;
|
|
1440
|
+
transformed.map = res.map;
|
|
1441
|
+
}
|
|
1442
|
+
const banner = resolveChunkAddon(entry.banner, "esm");
|
|
1443
|
+
const footer = resolveChunkAddon(entry.footer, "esm");
|
|
1444
|
+
transformed.code = addBannerFooter(transformed.code, banner, footer);
|
|
1445
|
+
if (entry.nodeProtocol) transformed.code = transformNodeProtocol(transformed.code, entry.nodeProtocol);
|
|
1446
|
+
return transformed;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
//#endregion
|
|
1450
|
+
//#region src/features/exports.ts
|
|
1451
|
+
/**
|
|
1452
|
+
* Generate package.json exports field based on build configuration
|
|
1453
|
+
*/
|
|
1454
|
+
async function generatePackageExports(packageRoot, buildConfig, exportsConfig = { enabled: true }) {
|
|
1455
|
+
if (!exportsConfig.enabled) return {};
|
|
1456
|
+
const exports = {};
|
|
1457
|
+
const baseDir = exportsConfig.baseDir || "dist";
|
|
1458
|
+
if (exportsConfig.custom) Object.assign(exports, exportsConfig.custom);
|
|
1459
|
+
if (buildConfig.entries) for (const entry of buildConfig.entries) {
|
|
1460
|
+
const exportEntries = await generateExportFromEntry(packageRoot, entry, baseDir, exportsConfig.includeTypes);
|
|
1461
|
+
for (const exportEntry of exportEntries) exports[exportEntry.key] = createExportValue(exportEntry);
|
|
1462
|
+
}
|
|
1463
|
+
if (!exports["."] && !exports["./index"]) {
|
|
1464
|
+
const mainExport = await findMainExport(packageRoot, baseDir);
|
|
1465
|
+
if (mainExport) exports["."] = mainExport;
|
|
1466
|
+
}
|
|
1467
|
+
return exports;
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Generate export entries from a build entry
|
|
1471
|
+
*/
|
|
1472
|
+
async function generateExportFromEntry(packageRoot, entry, baseDir, includeTypes) {
|
|
1473
|
+
const exports = [];
|
|
1474
|
+
if (entry.type === "bundle") {
|
|
1475
|
+
const exportKey = getExportKey(entry.input);
|
|
1476
|
+
const exportEntry = { key: exportKey };
|
|
1477
|
+
if (Array.isArray(entry.format)) for (const format of entry.format) {
|
|
1478
|
+
const outputPath = getOutputPath(entry, format, baseDir);
|
|
1479
|
+
assignFormatToExport(exportEntry, format, outputPath);
|
|
1480
|
+
}
|
|
1481
|
+
else if (entry.format) {
|
|
1482
|
+
const outputPath = getOutputPath(entry, entry.format, baseDir);
|
|
1483
|
+
assignFormatToExport(exportEntry, entry.format, outputPath);
|
|
1484
|
+
}
|
|
1485
|
+
if (includeTypes && entry.dts) {
|
|
1486
|
+
const typesPath = getTypesPath(entry, baseDir);
|
|
1487
|
+
exportEntry.types = typesPath;
|
|
1488
|
+
}
|
|
1489
|
+
exports.push(exportEntry);
|
|
1490
|
+
} else if (entry.type === "transform") {
|
|
1491
|
+
const transformExports = await discoverTransformExports(packageRoot, entry, baseDir, includeTypes);
|
|
1492
|
+
exports.push(...transformExports);
|
|
1493
|
+
}
|
|
1494
|
+
return exports;
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Get export key from input path
|
|
1498
|
+
*/
|
|
1499
|
+
function getExportKey(input) {
|
|
1500
|
+
if (input === "index.ts" || input === "src/index.ts") return ".";
|
|
1501
|
+
let key = input.replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
1502
|
+
key = key.replace(/^src\//, "");
|
|
1503
|
+
return key === "index" ? "." : `./${key}`;
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Get output path for a specific format
|
|
1507
|
+
*/
|
|
1508
|
+
function getOutputPath(entry, format, baseDir) {
|
|
1509
|
+
const extension = getExtensionForFormat(format);
|
|
1510
|
+
const basename$1 = getBasename(entry.input);
|
|
1511
|
+
if (format === "cjs") return `./${baseDir}/cjs/${basename$1}${extension}`;
|
|
1512
|
+
else if (format === "esm") return `./${baseDir}/${basename$1}${extension}`;
|
|
1513
|
+
else return `./${baseDir}/${format}/${basename$1}${extension}`;
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Get file extension for format
|
|
1517
|
+
*/
|
|
1518
|
+
function getExtensionForFormat(format) {
|
|
1519
|
+
switch (format) {
|
|
1520
|
+
case "esm": return ".mjs";
|
|
1521
|
+
case "cjs": return ".cjs";
|
|
1522
|
+
case "iife":
|
|
1523
|
+
case "umd": return ".js";
|
|
1524
|
+
default: return ".js";
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Get basename from input path
|
|
1529
|
+
*/
|
|
1530
|
+
function getBasename(input) {
|
|
1531
|
+
return input.replace(/\.(ts|js|tsx|jsx)$/, "").replace(/^src\//, "");
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Get types path for entry
|
|
1535
|
+
*/
|
|
1536
|
+
function getTypesPath(entry, baseDir) {
|
|
1537
|
+
const basename$1 = getBasename(entry.input);
|
|
1538
|
+
return `./${baseDir}/${basename$1}.d.ts`;
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Assign format-specific path to export entry
|
|
1542
|
+
*/
|
|
1543
|
+
function assignFormatToExport(exportEntry, format, path) {
|
|
1544
|
+
switch (format) {
|
|
1545
|
+
case "esm":
|
|
1546
|
+
exportEntry.import = path;
|
|
1547
|
+
break;
|
|
1548
|
+
case "cjs":
|
|
1549
|
+
exportEntry.require = path;
|
|
1550
|
+
break;
|
|
1551
|
+
default:
|
|
1552
|
+
exportEntry.import = path;
|
|
1553
|
+
break;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Create export value from export entry
|
|
1558
|
+
*/
|
|
1559
|
+
function createExportValue(entry) {
|
|
1560
|
+
const value = {};
|
|
1561
|
+
if (entry.types) value.types = entry.types;
|
|
1562
|
+
if (entry.import) value.import = entry.import;
|
|
1563
|
+
if (entry.require) value.require = entry.require;
|
|
1564
|
+
const keys = Object.keys(value);
|
|
1565
|
+
if (keys.length === 1 && !entry.types) return value[keys[0]];
|
|
1566
|
+
return value;
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Discover exports from transform entries
|
|
1570
|
+
*/
|
|
1571
|
+
async function discoverTransformExports(packageRoot, entry, baseDir, includeTypes) {
|
|
1572
|
+
const exports = [];
|
|
1573
|
+
const exportKey = getExportKey(entry.input);
|
|
1574
|
+
const exportEntry = {
|
|
1575
|
+
key: exportKey,
|
|
1576
|
+
import: `./${baseDir}/${getBasename(entry.input)}.mjs`
|
|
1577
|
+
};
|
|
1578
|
+
if (includeTypes && entry.dts) exportEntry.types = getTypesPath(entry, baseDir);
|
|
1579
|
+
exports.push(exportEntry);
|
|
1580
|
+
return exports;
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Find main export file
|
|
1584
|
+
*/
|
|
1585
|
+
async function findMainExport(packageRoot, baseDir) {
|
|
1586
|
+
const possibleMains = [
|
|
1587
|
+
"index.mjs",
|
|
1588
|
+
"index.js",
|
|
1589
|
+
"index.cjs"
|
|
1590
|
+
];
|
|
1591
|
+
for (const main of possibleMains) try {
|
|
1592
|
+
join(packageRoot, baseDir, main);
|
|
1593
|
+
const value = {};
|
|
1594
|
+
if (main.endsWith(".mjs")) value.import = `./${baseDir}/${main}`;
|
|
1595
|
+
else if (main.endsWith(".cjs")) value.require = `./${baseDir}/${main}`;
|
|
1596
|
+
else value.import = `./${baseDir}/${main}`;
|
|
1597
|
+
return value;
|
|
1598
|
+
} catch {
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
return null;
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Update package.json with generated exports
|
|
1605
|
+
*/
|
|
1606
|
+
async function updatePackageJsonExports(packageRoot, exports) {
|
|
1607
|
+
const packageJsonPath = join(packageRoot, "package.json");
|
|
1608
|
+
try {
|
|
1609
|
+
const content = await readFile(packageJsonPath, "utf-8");
|
|
1610
|
+
const packageJson = JSON.parse(content);
|
|
1611
|
+
packageJson.exports = exports;
|
|
1612
|
+
const updatedContent = `${JSON.stringify(packageJson, null, 2)}\n`;
|
|
1613
|
+
await writeFile(packageJsonPath, updatedContent, "utf-8");
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
throw new Error(`Failed to update package.json: ${error}`);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
//#endregion
|
|
1620
|
+
//#region src/features/logger.ts
|
|
1621
|
+
/**
|
|
1622
|
+
* Logger instance with configurable log level
|
|
1623
|
+
*/
|
|
1624
|
+
var Logger = class {
|
|
1625
|
+
level = "info";
|
|
1626
|
+
warningCount = 0;
|
|
1627
|
+
errorCount = 0;
|
|
1628
|
+
constructor(level = "info") {
|
|
1629
|
+
this.level = level;
|
|
1630
|
+
this.updateConsolaLevel();
|
|
1631
|
+
}
|
|
1632
|
+
setLevel(level) {
|
|
1633
|
+
this.level = level;
|
|
1634
|
+
this.updateConsolaLevel();
|
|
1635
|
+
}
|
|
1636
|
+
updateConsolaLevel() {
|
|
1637
|
+
const levelMap = {
|
|
1638
|
+
silent: 0,
|
|
1639
|
+
error: 1,
|
|
1640
|
+
warn: 2,
|
|
1641
|
+
info: 3,
|
|
1642
|
+
verbose: 4
|
|
1643
|
+
};
|
|
1644
|
+
consola.level = levelMap[this.level];
|
|
1645
|
+
}
|
|
1646
|
+
silent(message, ...args) {
|
|
1647
|
+
console.log(message, ...args);
|
|
1648
|
+
}
|
|
1649
|
+
error(message, ...args) {
|
|
1650
|
+
this.errorCount++;
|
|
1651
|
+
consola.error(message, ...args);
|
|
1652
|
+
}
|
|
1653
|
+
warn(message, ...args) {
|
|
1654
|
+
this.warningCount++;
|
|
1655
|
+
consola.warn(message, ...args);
|
|
1656
|
+
}
|
|
1657
|
+
info(message, ...args) {
|
|
1658
|
+
consola.info(message, ...args);
|
|
1659
|
+
}
|
|
1660
|
+
verbose(message, ...args) {
|
|
1661
|
+
if (this.level === "verbose") consola.debug(message, ...args);
|
|
1662
|
+
}
|
|
1663
|
+
success(message, ...args) {
|
|
1664
|
+
consola.success(message, ...args);
|
|
1665
|
+
}
|
|
1666
|
+
log(message, ...args) {
|
|
1667
|
+
consola.log(message, ...args);
|
|
1668
|
+
}
|
|
1669
|
+
getWarningCount() {
|
|
1670
|
+
return this.warningCount;
|
|
1671
|
+
}
|
|
1672
|
+
getErrorCount() {
|
|
1673
|
+
return this.errorCount;
|
|
1674
|
+
}
|
|
1675
|
+
resetCounts() {
|
|
1676
|
+
this.warningCount = 0;
|
|
1677
|
+
this.errorCount = 0;
|
|
1678
|
+
}
|
|
1679
|
+
shouldFailOnWarnings(failOnWarn) {
|
|
1680
|
+
return failOnWarn && this.warningCount > 0;
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
const logger = new Logger();
|
|
1684
|
+
/**
|
|
1685
|
+
* Configure global logger
|
|
1686
|
+
*/
|
|
1687
|
+
function configureLogger(level) {
|
|
1688
|
+
logger.setLevel(level);
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Reset warning and error counts
|
|
1692
|
+
*/
|
|
1693
|
+
function resetLogCounts() {
|
|
1694
|
+
logger.resetCounts();
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Check if build should fail due to warnings
|
|
1698
|
+
*/
|
|
1699
|
+
function shouldFailOnWarnings(failOnWarn) {
|
|
1700
|
+
return logger.shouldFailOnWarnings(failOnWarn);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
//#endregion
|
|
1704
|
+
//#region src/features/on-success.ts
|
|
1705
|
+
const execAsync = promisify(exec);
|
|
1706
|
+
/**
|
|
1707
|
+
* Execute onSuccess callback after successful build
|
|
1708
|
+
*/
|
|
1709
|
+
async function executeOnSuccess(onSuccess, result, cwd) {
|
|
1710
|
+
if (!onSuccess) return;
|
|
1711
|
+
try {
|
|
1712
|
+
if (typeof onSuccess === "string") {
|
|
1713
|
+
logger.verbose(`Executing onSuccess command: ${onSuccess}`);
|
|
1714
|
+
const { stdout, stderr } = await execAsync(onSuccess, { cwd });
|
|
1715
|
+
if (stdout) logger.verbose(`onSuccess stdout: ${stdout.trim()}`);
|
|
1716
|
+
if (stderr) logger.warn(`onSuccess stderr: ${stderr.trim()}`);
|
|
1717
|
+
} else {
|
|
1718
|
+
logger.verbose("Executing onSuccess callback function");
|
|
1719
|
+
await onSuccess(result);
|
|
1720
|
+
}
|
|
1721
|
+
logger.verbose("onSuccess callback completed successfully");
|
|
1722
|
+
} catch (error) {
|
|
1723
|
+
logger.error("onSuccess callback failed:", error);
|
|
1724
|
+
throw error;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
/**
|
|
1728
|
+
* Create build result object
|
|
1729
|
+
*/
|
|
1730
|
+
function createBuildResult(entries, startTime) {
|
|
1731
|
+
return {
|
|
1732
|
+
entries: entries.map((entry) => ({
|
|
1733
|
+
...entry,
|
|
1734
|
+
format: entry.format
|
|
1735
|
+
})),
|
|
1736
|
+
duration: Date.now() - startTime
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
//#endregion
|
|
1741
|
+
//#region src/features/vite-config.ts
|
|
1742
|
+
/**
|
|
1743
|
+
* Load Vite configuration and convert to robuild config
|
|
1744
|
+
*/
|
|
1745
|
+
async function loadViteConfig(cwd) {
|
|
1746
|
+
const configFiles = [
|
|
1747
|
+
"vite.config.ts",
|
|
1748
|
+
"vite.config.js",
|
|
1749
|
+
"vite.config.mts",
|
|
1750
|
+
"vite.config.mjs",
|
|
1751
|
+
"vitest.config.ts",
|
|
1752
|
+
"vitest.config.js"
|
|
1753
|
+
];
|
|
1754
|
+
let configPath;
|
|
1755
|
+
for (const file of configFiles) {
|
|
1756
|
+
const fullPath = resolve(cwd, file);
|
|
1757
|
+
if (existsSync(fullPath)) {
|
|
1758
|
+
configPath = fullPath;
|
|
1759
|
+
break;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
if (!configPath) {
|
|
1763
|
+
logger.warn("No Vite config file found, skipping Vite config loading");
|
|
1764
|
+
return {};
|
|
1765
|
+
}
|
|
1766
|
+
try {
|
|
1767
|
+
logger.verbose(`Loading Vite config from: ${configPath}`);
|
|
1768
|
+
const configModule = await import(configPath);
|
|
1769
|
+
const viteConfig = configModule.default || configModule;
|
|
1770
|
+
return convertViteConfig(viteConfig);
|
|
1771
|
+
} catch (error) {
|
|
1772
|
+
logger.error(`Failed to load Vite config from ${configPath}:`, error);
|
|
1773
|
+
return {};
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Convert Vite config to robuild config
|
|
1778
|
+
*/
|
|
1779
|
+
function convertViteConfig(viteConfig) {
|
|
1780
|
+
const config = {};
|
|
1781
|
+
if (viteConfig.build?.lib) {
|
|
1782
|
+
const lib = viteConfig.build.lib;
|
|
1783
|
+
const entries = [];
|
|
1784
|
+
if (typeof lib.entry === "string") entries.push({
|
|
1785
|
+
type: "bundle",
|
|
1786
|
+
input: lib.entry,
|
|
1787
|
+
format: convertFormats(lib.formats),
|
|
1788
|
+
globalName: lib.name,
|
|
1789
|
+
minify: viteConfig.build.minify,
|
|
1790
|
+
target: convertTarget(viteConfig.build.target),
|
|
1791
|
+
external: convertExternal(viteConfig.build.rollupOptions?.external)
|
|
1792
|
+
});
|
|
1793
|
+
else if (Array.isArray(lib.entry)) for (const entry of lib.entry) entries.push({
|
|
1794
|
+
type: "bundle",
|
|
1795
|
+
input: entry,
|
|
1796
|
+
format: convertFormats(lib.formats),
|
|
1797
|
+
globalName: lib.name,
|
|
1798
|
+
minify: viteConfig.build.minify,
|
|
1799
|
+
target: convertTarget(viteConfig.build.target),
|
|
1800
|
+
external: convertExternal(viteConfig.build.rollupOptions?.external)
|
|
1801
|
+
});
|
|
1802
|
+
else if (lib.entry && typeof lib.entry === "object") for (const [name, entry] of Object.entries(lib.entry)) entries.push({
|
|
1803
|
+
type: "bundle",
|
|
1804
|
+
input: entry,
|
|
1805
|
+
format: convertFormats(lib.formats),
|
|
1806
|
+
globalName: lib.name || name,
|
|
1807
|
+
minify: viteConfig.build.minify,
|
|
1808
|
+
target: convertTarget(viteConfig.build.target),
|
|
1809
|
+
external: convertExternal(viteConfig.build.rollupOptions?.external)
|
|
1810
|
+
});
|
|
1811
|
+
if (entries.length > 0) config.entries = entries;
|
|
1812
|
+
}
|
|
1813
|
+
return config;
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1816
|
+
* Convert Vite formats to robuild formats
|
|
1817
|
+
*/
|
|
1818
|
+
function convertFormats(formats) {
|
|
1819
|
+
if (!formats) return void 0;
|
|
1820
|
+
const formatMap = {
|
|
1821
|
+
es: "esm",
|
|
1822
|
+
cjs: "cjs",
|
|
1823
|
+
umd: "umd",
|
|
1824
|
+
iife: "iife"
|
|
1825
|
+
};
|
|
1826
|
+
return formats.map((format) => formatMap[format] || format).filter(Boolean);
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* Convert Vite target to robuild target
|
|
1830
|
+
*/
|
|
1831
|
+
function convertTarget(target) {
|
|
1832
|
+
if (!target) return void 0;
|
|
1833
|
+
const targetMap = {
|
|
1834
|
+
es2015: "es2015",
|
|
1835
|
+
es2016: "es2016",
|
|
1836
|
+
es2017: "es2017",
|
|
1837
|
+
es2018: "es2018",
|
|
1838
|
+
es2019: "es2019",
|
|
1839
|
+
es2020: "es2020",
|
|
1840
|
+
es2021: "es2021",
|
|
1841
|
+
es2022: "es2022",
|
|
1842
|
+
esnext: "esnext"
|
|
1843
|
+
};
|
|
1844
|
+
return targetMap[target] || void 0;
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Convert Vite external config to robuild external
|
|
1848
|
+
*/
|
|
1849
|
+
function convertExternal(external) {
|
|
1850
|
+
return external;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
//#endregion
|
|
1854
|
+
//#region src/features/workspace.ts
|
|
1855
|
+
/**
|
|
1856
|
+
* Discover workspace packages based on patterns
|
|
1857
|
+
*/
|
|
1858
|
+
async function discoverWorkspacePackages(workspaceRoot, patterns = ["packages/*", "apps/*"]) {
|
|
1859
|
+
const packages = [];
|
|
1860
|
+
for (const pattern of patterns) {
|
|
1861
|
+
const packagePaths = await glob(pattern, {
|
|
1862
|
+
cwd: workspaceRoot,
|
|
1863
|
+
onlyDirectories: true
|
|
1864
|
+
});
|
|
1865
|
+
for (const packagePath of packagePaths) {
|
|
1866
|
+
const fullPath = resolve(workspaceRoot, packagePath);
|
|
1867
|
+
const packageJsonPath = join(fullPath, "package.json");
|
|
1868
|
+
try {
|
|
1869
|
+
const packageJsonContent = await readFile(packageJsonPath, "utf-8");
|
|
1870
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
1871
|
+
packages.push({
|
|
1872
|
+
name: packageJson.name || packagePath,
|
|
1873
|
+
path: fullPath,
|
|
1874
|
+
packageJson
|
|
1875
|
+
});
|
|
1876
|
+
} catch {
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
return packages;
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Filter workspace packages based on filter patterns
|
|
1885
|
+
*/
|
|
1886
|
+
function filterWorkspacePackages(packages, filter, exclude) {
|
|
1887
|
+
let filtered = packages;
|
|
1888
|
+
if (filter) {
|
|
1889
|
+
const filters = Array.isArray(filter) ? filter : [filter];
|
|
1890
|
+
filtered = filtered.filter((pkg) => filters.some((f) => matchesPattern(pkg.name, f) || matchesPattern(pkg.path, f)));
|
|
1891
|
+
}
|
|
1892
|
+
if (exclude) {
|
|
1893
|
+
const excludes = Array.isArray(exclude) ? exclude : [exclude];
|
|
1894
|
+
filtered = filtered.filter((pkg) => !excludes.some((e) => matchesPattern(pkg.name, e) || matchesPattern(pkg.path, e)));
|
|
1895
|
+
}
|
|
1896
|
+
return filtered;
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Check if a string matches a pattern (supports wildcards)
|
|
1900
|
+
*/
|
|
1901
|
+
function matchesPattern(str, pattern) {
|
|
1902
|
+
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".").replace(/\[([^\]]+)\]/g, "[$1]");
|
|
1903
|
+
const regex = /* @__PURE__ */ new RegExp(`^${regexPattern}$`);
|
|
1904
|
+
return regex.test(str);
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Load workspace configuration from package.json or workspace config file
|
|
1908
|
+
*/
|
|
1909
|
+
async function loadWorkspaceConfig(workspaceRoot) {
|
|
1910
|
+
try {
|
|
1911
|
+
const packageJsonPath = join(workspaceRoot, "package.json");
|
|
1912
|
+
const packageJsonContent = await readFile(packageJsonPath, "utf-8");
|
|
1913
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
1914
|
+
if (packageJson.workspaces) {
|
|
1915
|
+
const workspaces = Array.isArray(packageJson.workspaces) ? packageJson.workspaces : packageJson.workspaces.packages || [];
|
|
1916
|
+
return { packages: workspaces };
|
|
1917
|
+
}
|
|
1918
|
+
try {
|
|
1919
|
+
const { load } = await import("js-yaml");
|
|
1920
|
+
const pnpmWorkspacePath = join(workspaceRoot, "pnpm-workspace.yaml");
|
|
1921
|
+
const pnpmWorkspaceContent = await readFile(pnpmWorkspacePath, "utf-8");
|
|
1922
|
+
const pnpmWorkspace = load(pnpmWorkspaceContent);
|
|
1923
|
+
if (pnpmWorkspace?.packages) return { packages: pnpmWorkspace.packages };
|
|
1924
|
+
} catch {}
|
|
1925
|
+
try {
|
|
1926
|
+
const lernaJsonPath = join(workspaceRoot, "lerna.json");
|
|
1927
|
+
const lernaJsonContent = await readFile(lernaJsonPath, "utf-8");
|
|
1928
|
+
const lernaJson = JSON.parse(lernaJsonContent);
|
|
1929
|
+
if (lernaJson.packages) return { packages: lernaJson.packages };
|
|
1930
|
+
} catch {}
|
|
1931
|
+
return null;
|
|
1932
|
+
} catch {
|
|
1933
|
+
return null;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Build workspace packages in dependency order
|
|
1938
|
+
*/
|
|
1939
|
+
async function buildWorkspacePackages(packages, buildFn) {
|
|
1940
|
+
await Promise.all(packages.map(buildFn));
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
//#endregion
|
|
1944
|
+
//#region src/features/ignore-watch.ts
|
|
1945
|
+
/**
|
|
1946
|
+
* Normalize ignore patterns
|
|
1947
|
+
*/
|
|
1948
|
+
function normalizeIgnorePatterns(patterns) {
|
|
1949
|
+
return patterns.map((pattern) => {
|
|
1950
|
+
if (pattern.startsWith("./")) return pattern.slice(2);
|
|
1951
|
+
if (pattern.startsWith("/")) return pattern.slice(1);
|
|
1952
|
+
return pattern;
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
//#endregion
|
|
1957
|
+
//#region src/watch.ts
|
|
1958
|
+
/**
|
|
1959
|
+
* Start watching files and rebuild on changes
|
|
1960
|
+
*/
|
|
1961
|
+
async function startWatch(config, ctx, buildFn) {
|
|
1962
|
+
const watchOptions = config.watch || {};
|
|
1963
|
+
if (!watchOptions.enabled) throw new Error("Watch mode is not enabled");
|
|
1964
|
+
const watchCtx = {
|
|
1965
|
+
config,
|
|
1966
|
+
ctx,
|
|
1967
|
+
buildFn,
|
|
1968
|
+
isBuilding: false,
|
|
1969
|
+
pendingRebuild: false
|
|
1970
|
+
};
|
|
1971
|
+
const watchPatterns = await getWatchPatterns(config, ctx, watchOptions);
|
|
1972
|
+
const ignorePatterns = getIgnorePatterns(config, watchOptions);
|
|
1973
|
+
consola.info(`š Starting watch mode...`);
|
|
1974
|
+
consola.info(`š Watching: ${colors.dim(watchPatterns.join(", "))}`);
|
|
1975
|
+
if (ignorePatterns.length > 0) consola.info(`š« Ignoring: ${colors.dim(ignorePatterns.join(", "))}`);
|
|
1976
|
+
const delay = watchOptions.delay ?? 100;
|
|
1977
|
+
if (delay > 0) consola.info(`ā±ļø Rebuild delay: ${colors.dim(`${delay}ms`)}`);
|
|
1978
|
+
const watcher = watch(watchPatterns, {
|
|
1979
|
+
ignored: ignorePatterns,
|
|
1980
|
+
ignoreInitial: watchOptions.ignoreInitial ?? false,
|
|
1981
|
+
persistent: true,
|
|
1982
|
+
followSymlinks: false,
|
|
1983
|
+
cwd: ctx.pkgDir
|
|
1984
|
+
});
|
|
1985
|
+
watcher.on("change", (path) => handleFileChange(watchCtx, path, "changed"));
|
|
1986
|
+
watcher.on("add", (path) => {
|
|
1987
|
+
if (watchOptions.watchNewFiles !== false) handleFileChange(watchCtx, path, "added");
|
|
1988
|
+
});
|
|
1989
|
+
watcher.on("unlink", (path) => handleFileChange(watchCtx, path, "removed"));
|
|
1990
|
+
watcher.on("error", (error) => {
|
|
1991
|
+
consola.error("ā Watch error:", error);
|
|
1992
|
+
});
|
|
1993
|
+
if (process.env.DEBUG) {
|
|
1994
|
+
watcher.on("addDir", (path) => consola.debug(`š Directory added: ${path}`));
|
|
1995
|
+
watcher.on("unlinkDir", (path) => consola.debug(`š Directory removed: ${path}`));
|
|
1996
|
+
}
|
|
1997
|
+
await new Promise((resolve$1) => {
|
|
1998
|
+
watcher.on("ready", () => {
|
|
1999
|
+
const watchedPaths = watcher.getWatched();
|
|
2000
|
+
const totalFiles = Object.values(watchedPaths).reduce((sum, files) => sum + files.length, 0);
|
|
2001
|
+
consola.success(`š Watch mode ready - watching ${totalFiles} files`);
|
|
2002
|
+
consola.info(`š” Press ${colors.cyan("Ctrl+C")} to stop watching`);
|
|
2003
|
+
resolve$1();
|
|
2004
|
+
});
|
|
2005
|
+
});
|
|
2006
|
+
return () => {
|
|
2007
|
+
if (watchCtx.rebuildTimer) clearTimeout(watchCtx.rebuildTimer);
|
|
2008
|
+
return watcher.close();
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Handle file change events
|
|
2013
|
+
*/
|
|
2014
|
+
function handleFileChange(watchCtx, filePath, changeType) {
|
|
2015
|
+
const { config, ctx } = watchCtx;
|
|
2016
|
+
const watchOptions = config.watch || {};
|
|
2017
|
+
const delay = watchOptions.delay ?? 100;
|
|
2018
|
+
const relativePath = relative(ctx.pkgDir, join(ctx.pkgDir, filePath));
|
|
2019
|
+
const formattedPath = fmtPath(relativePath);
|
|
2020
|
+
const changeIcon = changeType === "changed" ? "š" : changeType === "added" ? "ā" : "ā";
|
|
2021
|
+
consola.info(`${changeIcon} ${colors.cyan(formattedPath)} ${changeType}`);
|
|
2022
|
+
if (watchCtx.rebuildTimer) clearTimeout(watchCtx.rebuildTimer);
|
|
2023
|
+
watchCtx.pendingRebuild = true;
|
|
2024
|
+
watchCtx.rebuildTimer = setTimeout(() => {
|
|
2025
|
+
triggerRebuild(watchCtx);
|
|
2026
|
+
}, delay);
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Trigger a rebuild
|
|
2030
|
+
*/
|
|
2031
|
+
async function triggerRebuild(watchCtx) {
|
|
2032
|
+
const { config, buildFn } = watchCtx;
|
|
2033
|
+
if (watchCtx.isBuilding) return;
|
|
2034
|
+
if (!watchCtx.pendingRebuild) return;
|
|
2035
|
+
watchCtx.isBuilding = true;
|
|
2036
|
+
watchCtx.pendingRebuild = false;
|
|
2037
|
+
try {
|
|
2038
|
+
consola.info(`š Rebuilding...`);
|
|
2039
|
+
const start = Date.now();
|
|
2040
|
+
const buildConfig = {
|
|
2041
|
+
...config,
|
|
2042
|
+
watch: {
|
|
2043
|
+
...config.watch,
|
|
2044
|
+
enabled: false
|
|
2045
|
+
}
|
|
2046
|
+
};
|
|
2047
|
+
await buildFn(buildConfig);
|
|
2048
|
+
const duration = Date.now() - start;
|
|
2049
|
+
consola.success(`ā
Rebuild completed in ${duration}ms`);
|
|
2050
|
+
} catch (error) {
|
|
2051
|
+
consola.error("ā Rebuild failed:");
|
|
2052
|
+
if (error instanceof Error) {
|
|
2053
|
+
consola.error(` ${error.message}`);
|
|
2054
|
+
if (process.env.DEBUG && error.stack) consola.debug(error.stack);
|
|
2055
|
+
} else consola.error(` ${String(error)}`);
|
|
2056
|
+
consola.info("š Still watching for changes...");
|
|
2057
|
+
} finally {
|
|
2058
|
+
watchCtx.isBuilding = false;
|
|
2059
|
+
if (watchCtx.pendingRebuild) setTimeout(() => triggerRebuild(watchCtx), watchCtx.config.watch?.delay ?? 100);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Get patterns for files to watch
|
|
2064
|
+
*/
|
|
2065
|
+
async function getWatchPatterns(config, ctx, watchOptions) {
|
|
2066
|
+
if (watchOptions.include && watchOptions.include.length > 0) return watchOptions.include.map(normalizeGlobPattern);
|
|
2067
|
+
const patterns = /* @__PURE__ */ new Set();
|
|
2068
|
+
const addPattern = (pattern) => {
|
|
2069
|
+
if (!pattern) return;
|
|
2070
|
+
patterns.add(normalizeGlobPattern(pattern));
|
|
2071
|
+
};
|
|
2072
|
+
const addFilePatterns = (inputFile) => {
|
|
2073
|
+
const absoluteInput = resolveToAbsolute(inputFile, ctx);
|
|
2074
|
+
const filePattern = normalizeRelativeToPkg(absoluteInput, ctx);
|
|
2075
|
+
if (filePattern && filePattern !== ".") addPattern(filePattern);
|
|
2076
|
+
addPattern(getDirGlobPattern(dirname(absoluteInput), ctx));
|
|
2077
|
+
};
|
|
2078
|
+
const addDirectoryPatterns = (inputDir) => {
|
|
2079
|
+
const absoluteDir = resolveToAbsolute(inputDir, ctx);
|
|
2080
|
+
addPattern(getDirGlobPattern(absoluteDir, ctx));
|
|
2081
|
+
};
|
|
2082
|
+
for (const entry of config.entries || []) if (typeof entry === "string") {
|
|
2083
|
+
const [input] = entry.split(":");
|
|
2084
|
+
if (input.endsWith("/")) addDirectoryPatterns(input);
|
|
2085
|
+
else {
|
|
2086
|
+
const inputs = input.split(",");
|
|
2087
|
+
for (const inputFile of inputs) addFilePatterns(inputFile);
|
|
2088
|
+
}
|
|
2089
|
+
} else if (entry.type === "transform") addDirectoryPatterns(entry.input);
|
|
2090
|
+
else {
|
|
2091
|
+
const inputs = Array.isArray(entry.input) ? entry.input : [entry.input];
|
|
2092
|
+
for (const inputFile of inputs) addFilePatterns(inputFile);
|
|
2093
|
+
}
|
|
2094
|
+
if (patterns.size === 0) [
|
|
2095
|
+
"src/**/*",
|
|
2096
|
+
"*.ts",
|
|
2097
|
+
"*.js",
|
|
2098
|
+
"*.mjs",
|
|
2099
|
+
"*.json"
|
|
2100
|
+
].forEach((pattern) => addPattern(pattern));
|
|
2101
|
+
return Array.from(patterns);
|
|
2102
|
+
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Get patterns for files to ignore
|
|
2105
|
+
*/
|
|
2106
|
+
function getIgnorePatterns(config, watchOptions) {
|
|
2107
|
+
const defaultIgnores = [
|
|
2108
|
+
"**/node_modules/**",
|
|
2109
|
+
"**/dist/**",
|
|
2110
|
+
"**/build/**",
|
|
2111
|
+
"**/coverage/**",
|
|
2112
|
+
"**/.git/**",
|
|
2113
|
+
"**/.DS_Store",
|
|
2114
|
+
"**/Thumbs.db",
|
|
2115
|
+
"**/*.log",
|
|
2116
|
+
"**/tmp/**",
|
|
2117
|
+
"**/temp/**"
|
|
2118
|
+
];
|
|
2119
|
+
const allIgnores = [...defaultIgnores];
|
|
2120
|
+
if (watchOptions.exclude && watchOptions.exclude.length > 0) allIgnores.push(...watchOptions.exclude);
|
|
2121
|
+
if (config.ignoreWatch && config.ignoreWatch.length > 0) allIgnores.push(...normalizeIgnorePatterns(config.ignoreWatch));
|
|
2122
|
+
return allIgnores;
|
|
2123
|
+
}
|
|
2124
|
+
function resolveToAbsolute(path, ctx) {
|
|
2125
|
+
return isAbsolute(path) ? path : join(ctx.pkgDir, path);
|
|
2126
|
+
}
|
|
2127
|
+
function normalizeGlobPattern(pattern) {
|
|
2128
|
+
return pattern.replace(/\\/g, "/");
|
|
2129
|
+
}
|
|
2130
|
+
function normalizeRelativeToPkg(target, ctx) {
|
|
2131
|
+
const relativePath = relative(ctx.pkgDir, target);
|
|
2132
|
+
if (!relativePath || relativePath === "") return ".";
|
|
2133
|
+
if (relativePath.includes(":")) return normalizeGlobPattern(target);
|
|
2134
|
+
return normalizeGlobPattern(relativePath);
|
|
2135
|
+
}
|
|
2136
|
+
function getDirGlobPattern(directory, ctx) {
|
|
2137
|
+
const normalizedDir = normalizeRelativeToPkg(directory, ctx);
|
|
2138
|
+
if (!normalizedDir || normalizedDir === "." || normalizedDir === "./") return "**/*";
|
|
2139
|
+
return `${stripTrailingSlash(normalizedDir)}/**/*`;
|
|
2140
|
+
}
|
|
2141
|
+
function stripTrailingSlash(path) {
|
|
2142
|
+
return path.replace(/\/+$/, "");
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
//#endregion
|
|
2146
|
+
//#region src/build.ts
|
|
2147
|
+
/**
|
|
2148
|
+
* Build dist/ from src/
|
|
2149
|
+
*/
|
|
2150
|
+
async function build(config) {
|
|
2151
|
+
const startTime = Date.now();
|
|
2152
|
+
if (config.logLevel) configureLogger(config.logLevel);
|
|
2153
|
+
resetLogCounts();
|
|
2154
|
+
const pkgDir = normalizePath(config.cwd);
|
|
2155
|
+
const pkg = await readJSON(join(pkgDir, "package.json")).catch(() => ({}));
|
|
2156
|
+
const ctx = {
|
|
2157
|
+
pkg,
|
|
2158
|
+
pkgDir
|
|
2159
|
+
};
|
|
2160
|
+
if (config.workspace) return buildWorkspace(config, pkgDir);
|
|
2161
|
+
let finalConfig = config;
|
|
2162
|
+
if (config.fromVite) {
|
|
2163
|
+
logger.verbose("Loading configuration from Vite config file");
|
|
2164
|
+
const viteConfig = await loadViteConfig(pkgDir);
|
|
2165
|
+
finalConfig = {
|
|
2166
|
+
...viteConfig,
|
|
2167
|
+
...config
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
if (finalConfig.watch?.enabled) {
|
|
2171
|
+
logger.info(`š Starting watch mode for \`${ctx.pkg.name || "<no name>"}\` (\`${ctx.pkgDir}\`)`);
|
|
2172
|
+
await performBuild(finalConfig, ctx, startTime);
|
|
2173
|
+
const stopWatch = await startWatch(finalConfig, ctx, build);
|
|
2174
|
+
const cleanup = () => {
|
|
2175
|
+
consola.info("š Stopping watch mode...");
|
|
2176
|
+
stopWatch();
|
|
2177
|
+
process.exit(0);
|
|
2178
|
+
};
|
|
2179
|
+
process.on("SIGINT", cleanup);
|
|
2180
|
+
process.on("SIGTERM", cleanup);
|
|
2181
|
+
return new Promise(() => {});
|
|
2182
|
+
}
|
|
2183
|
+
logger.info(`š¦ Building \`${ctx.pkg.name || "<no name>"}\` (\`${ctx.pkgDir}\`)`);
|
|
2184
|
+
await performBuild(finalConfig, ctx, startTime);
|
|
2185
|
+
}
|
|
2186
|
+
/**
|
|
2187
|
+
* Perform the actual build process
|
|
2188
|
+
*/
|
|
2189
|
+
async function performBuild(config, ctx, startTime) {
|
|
2190
|
+
const start = Date.now();
|
|
2191
|
+
const hooks = config.hooks || {};
|
|
2192
|
+
await hooks.start?.(ctx);
|
|
2193
|
+
const entries = (config.entries || []).map((rawEntry) => {
|
|
2194
|
+
let entry;
|
|
2195
|
+
if (typeof rawEntry === "string") {
|
|
2196
|
+
const [input, outDir] = rawEntry.split(":");
|
|
2197
|
+
entry = input.endsWith("/") ? {
|
|
2198
|
+
type: "transform",
|
|
2199
|
+
input,
|
|
2200
|
+
outDir
|
|
2201
|
+
} : {
|
|
2202
|
+
type: "bundle",
|
|
2203
|
+
input: input.split(","),
|
|
2204
|
+
outDir
|
|
2205
|
+
};
|
|
2206
|
+
} else entry = rawEntry;
|
|
2207
|
+
if (!entry.input) throw new Error(`Build entry missing \`input\`: ${JSON.stringify(entry, null, 2)}`);
|
|
2208
|
+
entry = { ...entry };
|
|
2209
|
+
entry.outDir = normalizePath(entry.outDir || "dist", ctx.pkgDir);
|
|
2210
|
+
entry.input = Array.isArray(entry.input) ? entry.input.map((p) => normalizePath(p, ctx.pkgDir)) : normalizePath(entry.input, ctx.pkgDir);
|
|
2211
|
+
return entry;
|
|
2212
|
+
});
|
|
2213
|
+
await hooks.entries?.(entries, ctx);
|
|
2214
|
+
const outDirs = [];
|
|
2215
|
+
for (const outDir of entries.map((e) => e.outDir).sort()) if (!outDirs.some((dir) => outDir.startsWith(dir))) outDirs.push(outDir);
|
|
2216
|
+
for (const entry of entries) await (entry.type === "bundle" ? rolldownBuild(ctx, entry, hooks, config) : transformDir(ctx, entry));
|
|
2217
|
+
await hooks.end?.(ctx);
|
|
2218
|
+
if (shouldFailOnWarnings(config.failOnWarn || false)) throw new Error("Build failed due to warnings");
|
|
2219
|
+
const dirSize = analyzeDir(outDirs);
|
|
2220
|
+
logger.info(colors.dim(`\nΣ Total dist byte size: ${colors.underline(prettyBytes(dirSize.size))} (${colors.underline(dirSize.files)} files)`));
|
|
2221
|
+
const duration = Date.now() - start;
|
|
2222
|
+
logger.success(`\nā
robuild finished in ${duration}ms`);
|
|
2223
|
+
if (config.onSuccess) {
|
|
2224
|
+
const buildResult = createBuildResult([], startTime);
|
|
2225
|
+
await executeOnSuccess(config.onSuccess, buildResult, ctx.pkgDir);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
function normalizePath(path, resolveFrom) {
|
|
2229
|
+
return typeof path === "string" && isAbsolute(path) ? path : path instanceof URL ? fileURLToPath(path) : resolve(resolveFrom || ".", path || ".");
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Build workspace packages
|
|
2233
|
+
*/
|
|
2234
|
+
async function buildWorkspace(config, workspaceRoot) {
|
|
2235
|
+
logger.info("š¢ Building workspace packages...");
|
|
2236
|
+
const workspaceConfig = await loadWorkspaceConfig(workspaceRoot);
|
|
2237
|
+
if (!workspaceConfig) throw new Error("No workspace configuration found");
|
|
2238
|
+
const allPackages = await discoverWorkspacePackages(workspaceRoot, workspaceConfig.packages);
|
|
2239
|
+
if (allPackages.length === 0) {
|
|
2240
|
+
logger.warn("No packages found in workspace");
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
const filteredPackages = filterWorkspacePackages(allPackages, config.filter || config.workspace?.filter, config.workspace?.exclude);
|
|
2244
|
+
if (filteredPackages.length === 0) {
|
|
2245
|
+
logger.warn("No packages match the filter criteria");
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
logger.info(`Building ${filteredPackages.length} packages`);
|
|
2249
|
+
const buildPackage = async (pkg) => {
|
|
2250
|
+
logger.info(`š¦ Building ${pkg.name}...`);
|
|
2251
|
+
try {
|
|
2252
|
+
const packageConfig = {
|
|
2253
|
+
...config,
|
|
2254
|
+
cwd: pkg.path,
|
|
2255
|
+
workspace: void 0
|
|
2256
|
+
};
|
|
2257
|
+
await build(packageConfig);
|
|
2258
|
+
if (config.exports?.enabled) {
|
|
2259
|
+
const exportsConfig = {
|
|
2260
|
+
enabled: true,
|
|
2261
|
+
...config.exports
|
|
2262
|
+
};
|
|
2263
|
+
const exports = await generatePackageExports(pkg.path, packageConfig, exportsConfig);
|
|
2264
|
+
if (config.exports.autoUpdate) {
|
|
2265
|
+
await updatePackageJsonExports(pkg.path, exports);
|
|
2266
|
+
logger.info(`Updated exports for ${pkg.name}`);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
logger.success(`Built ${pkg.name}`);
|
|
2270
|
+
} catch (error) {
|
|
2271
|
+
logger.error(`Failed to build ${pkg.name}:`, error);
|
|
2272
|
+
throw error;
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
await buildWorkspacePackages(filteredPackages, buildPackage);
|
|
2276
|
+
logger.success(`Successfully built ${filteredPackages.length} packages`);
|
|
2277
|
+
}
|
|
2278
|
+
function readJSON(specifier) {
|
|
2279
|
+
return import(specifier, { with: { type: "json" } }).then((r) => r.default);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
//#endregion
|
|
2283
|
+
export { build };
|