robuild 0.1.5 → 0.1.7

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 CHANGED
@@ -97,21 +97,17 @@ Visit our documentation site for detailed guides, API reference, and examples.
97
97
  <!-- coverage-start -->
98
98
  | File | Stmts | Branch | Funcs | Lines |
99
99
  |------|-------|--------|-------|-------|
100
- | **All files** | **45.60%** | **40.43%** | **45.72%** | **45.78%** |
100
+ | **All files** | **86.44%** | **80.12%** | **92.94%** | **86.32%** |
101
101
  | src/build.ts | 84.15% | 67.74% | 100.00% | 83.54% |
102
- | src/builders | 79.83% | 71.76% | 85.19% | 79.83% |
103
- | src/config | 42.31% | 26.21% | 56.67% | 42.06% |
104
- | src/config.ts | 0.00% | 0.00% | 0.00% | 0.00% |
105
- | src/core | 67.74% | 57.14% | 63.16% | 67.74% |
106
- | src/deprecated | 0.00% | 0.00% | 0.00% | 0.00% |
107
- | src/index.ts | 0.00% | 0.00% | 0.00% | 0.00% |
108
- | src/plugins | 7.50% | 8.33% | 18.42% | 7.63% |
109
- | src/plugins/builtin | 16.46% | 18.03% | 23.08% | 16.51% |
110
- | src/plugins/extras | 0.00% | 0.00% | 0.00% | 0.00% |
102
+ | src/builders | 89.89% | 82.80% | 96.43% | 89.89% |
103
+ | src/config | 85.38% | 67.96% | 100.00% | 84.92% |
104
+ | src/core | 74.19% | 57.14% | 68.42% | 74.19% |
105
+ | src/plugins | 97.50% | 95.37% | 97.37% | 97.46% |
106
+ | src/plugins/builtin | 88.15% | 84.92% | 95.59% | 88.12% |
107
+ | src/plugins/extras | 96.55% | 100.00% | 100.00% | 96.30% |
111
108
  | src/transforms | 71.43% | 63.77% | 81.82% | 71.27% |
112
- | src/types.ts | 0.00% | 0.00% | 0.00% | 0.00% |
113
- | src/utils | 73.87% | 61.17% | 95.65% | 73.87% |
114
- | src/watch.ts | 0.00% | 0.00% | 0.00% | 0.00% |
109
+ | src/utils | 88.29% | 85.44% | 100.00% | 88.29% |
110
+ | src/watch.ts | 81.48% | 81.82% | 57.14% | 81.48% |
115
111
  <!-- coverage-end -->
116
112
 
117
113
  Run coverage locally:
@@ -132,5 +128,5 @@ pnpm test:coverage
132
128
  [bundle-href]: https://bundlephobia.com/result?p=robuild
133
129
  [license-src]: https://img.shields.io/github/license/Sunny-117/robuild.svg?style=flat&colorA=080f12&colorB=1fa669
134
130
  [license-href]: https://github.com/Sunny-117/robuild/blob/main/LICENSE
135
- [coverage-src]: https://img.shields.io/badge/coverage-45.6%25-yellow?style=flat&colorA=080f12
131
+ [coverage-src]: https://img.shields.io/badge/coverage-86.4%25-brightgreen?style=flat&colorA=080f12
136
132
  [coverage-href]: #test-coverage
@@ -3,7 +3,7 @@ import { builtinModules } from "node:module";
3
3
  import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
4
4
  import { colors } from "consola/utils";
5
5
  import prettyBytes from "pretty-bytes";
6
- import { cp, mkdir, readFile, readdir, rm, symlink, writeFile } from "node:fs/promises";
6
+ import { cp, mkdir, readFile, readdir, rm, symlink, unlink, writeFile } from "node:fs/promises";
7
7
  import { resolveModulePath } from "exsolve";
8
8
  import { parseSync } from "oxc-parser";
9
9
  import { rolldown, watch } from "rolldown";
@@ -12,8 +12,8 @@ import { existsSync, promises, readFileSync, readdirSync, statSync, writeFileSyn
12
12
  import { fileURLToPath, pathToFileURL } from "node:url";
13
13
  import { gzipSync } from "node:zlib";
14
14
  import { minify } from "oxc-minify";
15
- import { consola } from "consola";
16
15
  import { createHash } from "node:crypto";
16
+ import { consola } from "consola";
17
17
  import { glob } from "glob";
18
18
  import MagicString from "magic-string";
19
19
  import { transform } from "oxc-transform";
@@ -21,95 +21,6 @@ import { glob as glob$1 } from "tinyglobby";
21
21
  import { exec } from "node:child_process";
22
22
  import { promisify } from "node:util";
23
23
 
24
- //#region src/core/logger.ts
25
- /**
26
- * Logger instance with configurable log level
27
- */
28
- var Logger = class {
29
- level = "info";
30
- warningCount = 0;
31
- errorCount = 0;
32
- constructor(level = "info") {
33
- this.level = level;
34
- this.updateConsolaLevel();
35
- }
36
- setLevel(level) {
37
- this.level = level;
38
- this.updateConsolaLevel();
39
- }
40
- updateConsolaLevel() {
41
- consola.level = {
42
- silent: 0,
43
- error: 1,
44
- warn: 2,
45
- info: 3,
46
- verbose: 4
47
- }[this.level];
48
- }
49
- silent(message, ...args) {
50
- consola.log(message, ...args);
51
- }
52
- error(message, ...args) {
53
- this.errorCount++;
54
- consola.error(message, ...args);
55
- }
56
- warn(message, ...args) {
57
- this.warningCount++;
58
- consola.warn(message, ...args);
59
- }
60
- info(message, ...args) {
61
- consola.info(message, ...args);
62
- }
63
- verbose(message, ...args) {
64
- if (this.level === "verbose") consola.debug(message, ...args);
65
- }
66
- success(message, ...args) {
67
- consola.success(message, ...args);
68
- }
69
- log(message, ...args) {
70
- consola.log(message, ...args);
71
- }
72
- /**
73
- * Debug output - only visible with INSPECT_BUILD env var
74
- */
75
- debug(message, ...args) {
76
- if (process.env.INSPECT_BUILD) consola.log(message, ...args);
77
- }
78
- getWarningCount() {
79
- return this.warningCount;
80
- }
81
- getErrorCount() {
82
- return this.errorCount;
83
- }
84
- resetCounts() {
85
- this.warningCount = 0;
86
- this.errorCount = 0;
87
- }
88
- shouldFailOnWarnings(failOnWarn) {
89
- return failOnWarn && this.warningCount > 0;
90
- }
91
- };
92
- const logger = new Logger();
93
- /**
94
- * Configure global logger
95
- */
96
- function configureLogger(level) {
97
- logger.setLevel(level);
98
- }
99
- /**
100
- * Reset warning and error counts
101
- */
102
- function resetLogCounts() {
103
- logger.resetCounts();
104
- }
105
- /**
106
- * Check if build should fail due to warnings
107
- */
108
- function shouldFailOnWarnings(failOnWarn) {
109
- return logger.shouldFailOnWarnings(failOnWarn);
110
- }
111
-
112
- //#endregion
113
24
  //#region src/utils/extensions.ts
114
25
  /**
115
26
  * Get file extension for a given format (with leading dot).
@@ -356,14 +267,16 @@ function analyzeDir(dir) {
356
267
  throw error;
357
268
  }
358
269
  }
270
+ /**
271
+ * Calculate size metrics for a built file.
272
+ * Reads the file directly from disk instead of rebuilding with rolldown.
273
+ *
274
+ * @param dir - The output directory
275
+ * @param entry - The entry filename
276
+ * @returns Size metrics (raw, minified, gzipped)
277
+ */
359
278
  async function distSize(dir, entry) {
360
- const { output } = await (await rolldown({
361
- input: join(dir, entry),
362
- plugins: [],
363
- platform: "neutral",
364
- external: (id) => id[0] !== "." && !id.startsWith(dir)
365
- })).generate({ codeSplitting: false });
366
- const code = output[0].code;
279
+ const code = readFileSync(join(dir, entry), "utf-8");
367
280
  const { code: minified } = await minify(entry, code);
368
281
  return {
369
282
  size: Buffer.byteLength(code),
@@ -371,32 +284,17 @@ async function distSize(dir, entry) {
371
284
  minGzipSize: gzipSync(minified).length
372
285
  };
373
286
  }
374
- async function sideEffectSize(dir, entry) {
375
- const { output } = await (await rolldown({
376
- input: "#entry",
377
- platform: "neutral",
378
- external: (id) => id[0] !== "." && !id.startsWith(dir),
379
- plugins: [{
380
- name: "virtual-entry",
381
- async resolveId(id, importer, opts) {
382
- if (id === "#entry") return { id };
383
- const resolved = await this.resolve(id, importer, opts);
384
- if (!resolved) return null;
385
- resolved.moduleSideEffects = null;
386
- return resolved;
387
- },
388
- load(id) {
389
- if (id === "#entry") return `import * as _lib from "${join(dir, entry)}";`;
390
- }
391
- }]
392
- })).generate({ codeSplitting: false });
393
- if (process.env.INSPECT_BUILD) {
394
- logger.debug("---------[side effects]---------");
395
- logger.debug(entry);
396
- logger.debug(output[0].code);
397
- logger.debug("-------------------------------");
398
- }
399
- return Buffer.byteLength(output[0].code.trim());
287
+ /**
288
+ * Calculate side effect size.
289
+ * For now, returns 0 as calculating true side effects requires complex analysis.
290
+ * This avoids the expensive rolldown rebuild that was causing performance issues.
291
+ *
292
+ * @param _dir - The output directory (unused)
293
+ * @param _entry - The entry filename (unused)
294
+ * @returns Side effect size (always 0 for now)
295
+ */
296
+ async function sideEffectSize(_dir, _entry) {
297
+ return 0;
400
298
  }
401
299
 
402
300
  //#endregion
@@ -541,6 +439,95 @@ function resolveExternalConfig(ctx, options) {
541
439
  return externalDeps;
542
440
  }
543
441
 
442
+ //#endregion
443
+ //#region src/core/logger.ts
444
+ /**
445
+ * Logger instance with configurable log level
446
+ */
447
+ var Logger = class {
448
+ level = "info";
449
+ warningCount = 0;
450
+ errorCount = 0;
451
+ constructor(level = "info") {
452
+ this.level = level;
453
+ this.updateConsolaLevel();
454
+ }
455
+ setLevel(level) {
456
+ this.level = level;
457
+ this.updateConsolaLevel();
458
+ }
459
+ updateConsolaLevel() {
460
+ consola.level = {
461
+ silent: 0,
462
+ error: 1,
463
+ warn: 2,
464
+ info: 3,
465
+ verbose: 4
466
+ }[this.level];
467
+ }
468
+ silent(message, ...args) {
469
+ consola.log(message, ...args);
470
+ }
471
+ error(message, ...args) {
472
+ this.errorCount++;
473
+ consola.error(message, ...args);
474
+ }
475
+ warn(message, ...args) {
476
+ this.warningCount++;
477
+ consola.warn(message, ...args);
478
+ }
479
+ info(message, ...args) {
480
+ consola.info(message, ...args);
481
+ }
482
+ verbose(message, ...args) {
483
+ if (this.level === "verbose") consola.debug(message, ...args);
484
+ }
485
+ success(message, ...args) {
486
+ consola.success(message, ...args);
487
+ }
488
+ log(message, ...args) {
489
+ consola.log(message, ...args);
490
+ }
491
+ /**
492
+ * Debug output - only visible with INSPECT_BUILD env var
493
+ */
494
+ debug(message, ...args) {
495
+ if (process.env.INSPECT_BUILD) consola.log(message, ...args);
496
+ }
497
+ getWarningCount() {
498
+ return this.warningCount;
499
+ }
500
+ getErrorCount() {
501
+ return this.errorCount;
502
+ }
503
+ resetCounts() {
504
+ this.warningCount = 0;
505
+ this.errorCount = 0;
506
+ }
507
+ shouldFailOnWarnings(failOnWarn) {
508
+ return failOnWarn && this.warningCount > 0;
509
+ }
510
+ };
511
+ const logger = new Logger();
512
+ /**
513
+ * Configure global logger
514
+ */
515
+ function configureLogger(level) {
516
+ logger.setLevel(level);
517
+ }
518
+ /**
519
+ * Reset warning and error counts
520
+ */
521
+ function resetLogCounts() {
522
+ logger.resetCounts();
523
+ }
524
+ /**
525
+ * Check if build should fail due to warnings
526
+ */
527
+ function shouldFailOnWarnings(failOnWarn) {
528
+ return logger.shouldFailOnWarnings(failOnWarn);
529
+ }
530
+
544
531
  //#endregion
545
532
  //#region src/plugins/builtin/glob-import.ts
546
533
  /**
@@ -879,6 +866,54 @@ function createSkipNodeModulesPlugin(options) {
879
866
  };
880
867
  }
881
868
 
869
+ //#endregion
870
+ //#region src/plugins/builtin/wasm.ts
871
+ /**
872
+ * Default WASM configuration
873
+ */
874
+ const DEFAULT_WASM_CONFIG = {
875
+ enabled: true,
876
+ maxFileSize: 14 * 1024,
877
+ fileName: "[hash][extname]",
878
+ publicPath: "",
879
+ targetEnv: "auto"
880
+ };
881
+ /**
882
+ * Normalize WASM configuration
883
+ */
884
+ function normalizeWasmConfig(config) {
885
+ if (!config) return null;
886
+ if (config === true) return { ...DEFAULT_WASM_CONFIG };
887
+ if (!config.enabled) return null;
888
+ return {
889
+ ...DEFAULT_WASM_CONFIG,
890
+ ...config
891
+ };
892
+ }
893
+ /**
894
+ * Create WASM plugin for robuild
895
+ *
896
+ * This is a wrapper that dynamically imports rolldown-plugin-wasm
897
+ * to avoid making it a required dependency.
898
+ *
899
+ * @param config WASM configuration options
900
+ * @returns Rolldown plugin for WASM support
901
+ */
902
+ async function createWasmPlugin(config) {
903
+ try {
904
+ const { wasm } = await import("./dist-fY5yKI94.mjs");
905
+ return wasm({
906
+ maxFileSize: config.maxFileSize,
907
+ fileName: config.fileName,
908
+ publicPath: config.publicPath,
909
+ targetEnv: config.targetEnv
910
+ });
911
+ } catch (error) {
912
+ if (error.code === "ERR_MODULE_NOT_FOUND") return null;
913
+ throw error;
914
+ }
915
+ }
916
+
882
917
  //#endregion
883
918
  //#region src/transforms/banner.ts
884
919
  /**
@@ -995,6 +1030,8 @@ async function rolldownBuild(ctx, entry, hooks, config) {
995
1030
  if (entry.dtsOnly) {
996
1031
  logger.info("Running in dtsOnly mode - only generating declaration files");
997
1032
  entry.dts = entry.dts === false ? true : entry.dts || true;
1033
+ formats.length = 0;
1034
+ formats.push("esm");
998
1035
  }
999
1036
  if (entry.stub) {
1000
1037
  for (const [distName, srcPath] of Object.entries(inputs)) {
@@ -1046,6 +1083,15 @@ async function rolldownBuild(ctx, entry, hooks, config) {
1046
1083
  resolveId: skipPlugin.resolveId
1047
1084
  });
1048
1085
  }
1086
+ const wasmConfig = normalizeWasmConfig(entry.wasm ?? config?.wasm);
1087
+ if (wasmConfig) {
1088
+ const wasmPlugin = await createWasmPlugin(wasmConfig);
1089
+ if (wasmPlugin) rolldownPlugins.push(wasmPlugin);
1090
+ else {
1091
+ logger.warn("WASM support is enabled but rolldown-plugin-wasm is not installed.");
1092
+ logger.warn("Install it with: pnpm add -D rolldown-plugin-wasm");
1093
+ }
1094
+ }
1049
1095
  rolldownPlugins.push(...pluginManager.getRolldownPlugins());
1050
1096
  const moduleTypes = {};
1051
1097
  if (entry.loaders) for (const [ext, config] of Object.entries(entry.loaders)) moduleTypes[ext] = config.loader;
@@ -1071,13 +1117,15 @@ async function rolldownBuild(ctx, entry, hooks, config) {
1071
1117
  plugins: [...rolldownPlugins, ...Array.isArray(userPlugins) ? userPlugins : userPlugins ? [userPlugins] : []]
1072
1118
  };
1073
1119
  await hooks.rolldownConfig?.(baseRolldownConfig, ctx);
1074
- const allOutputEntries = [];
1075
1120
  const filePathMap = /* @__PURE__ */ new Map();
1076
- for (const format of formats) {
1121
+ const buildFormat = async (format) => {
1077
1122
  const extension = getFormatExtension(format, platform, entry.fixedExtension);
1078
1123
  const formatConfig = { ...baseRolldownConfig };
1079
1124
  if (entry.dts !== false && (format === "es" || format === "esm" || format === "module")) {
1080
- const dtsPlugins = dts({ ...entry.dts });
1125
+ const dtsPlugins = dts({
1126
+ cwd: ctx.pkgDir,
1127
+ ...typeof entry.dts === "object" ? entry.dts : {}
1128
+ });
1081
1129
  formatConfig.plugins = [...Array.isArray(formatConfig.plugins) ? formatConfig.plugins : [formatConfig.plugins], ...Array.isArray(dtsPlugins) ? dtsPlugins : [dtsPlugins]];
1082
1130
  }
1083
1131
  const res = await rolldown(formatConfig);
@@ -1128,6 +1176,7 @@ async function rolldownBuild(ctx, entry, hooks, config) {
1128
1176
  }
1129
1177
  return Array.from(deps).sort();
1130
1178
  };
1179
+ const formatOutputEntries = [];
1131
1180
  for (const chunk of output) {
1132
1181
  if (chunk.type !== "chunk" || !chunk.isEntry) continue;
1133
1182
  if (chunk.fileName.endsWith("ts")) continue;
@@ -1146,7 +1195,7 @@ async function rolldownBuild(ctx, entry, hooks, config) {
1146
1195
  finalFilePath = hashedFilePath;
1147
1196
  }
1148
1197
  filePathMap.set(finalFileName, finalFilePath);
1149
- allOutputEntries.push({
1198
+ formatOutputEntries.push({
1150
1199
  format,
1151
1200
  name: finalFileName,
1152
1201
  exports: chunk.exports,
@@ -1155,6 +1204,24 @@ async function rolldownBuild(ctx, entry, hooks, config) {
1155
1204
  sideEffectSize: await sideEffectSize(formatOutDir, finalFileName)
1156
1205
  });
1157
1206
  }
1207
+ return formatOutputEntries;
1208
+ };
1209
+ const allOutputEntries = (await Promise.all(formats.map(buildFormat))).flat();
1210
+ if (entry.dtsOnly) {
1211
+ const jsFilesToDelete = [];
1212
+ for (const outputEntry of allOutputEntries) {
1213
+ const filePath = filePathMap.get(outputEntry.name);
1214
+ if (filePath && !filePath.endsWith(".d.ts") && !filePath.endsWith(".d.mts") && !filePath.endsWith(".d.cts")) jsFilesToDelete.push(filePath);
1215
+ }
1216
+ for (const filePath of jsFilesToDelete) try {
1217
+ await unlink(filePath);
1218
+ try {
1219
+ await unlink(`${filePath}.map`);
1220
+ } catch {}
1221
+ } catch {}
1222
+ const dtsEntries = allOutputEntries.filter((o) => o.name.endsWith(".d.ts") || o.name.endsWith(".d.mts") || o.name.endsWith(".d.cts"));
1223
+ allOutputEntries.length = 0;
1224
+ allOutputEntries.push(...dtsEntries);
1158
1225
  }
1159
1226
  if (entry.copy) await copyFiles(ctx.pkgDir, fullOutDir, entry.copy);
1160
1227
  await pluginManager.executeRobuildBuildEnd({ allOutputEntries });
@@ -1765,7 +1832,7 @@ function createBuildResult(entries, startTime) {
1765
1832
  * Perform watch build using rolldown's built-in watch mode
1766
1833
  */
1767
1834
  async function performWatchBuild(config, ctx, startTime) {
1768
- const { performBuild } = await import("./build-DdGjMqZ-.mjs");
1835
+ const { performBuild } = await import("./build-BUwxGP7o.mjs");
1769
1836
  await performBuild(config, ctx, startTime);
1770
1837
  const bundleEntries = (config.entries || []).filter((entry) => {
1771
1838
  if (typeof entry === "string") return !entry.endsWith("/");
@@ -1967,7 +2034,7 @@ async function performBuild(config, ctx, startTime) {
1967
2034
  await hooks.entries?.(entries, ctx);
1968
2035
  const outDirs = [];
1969
2036
  for (const outDir of entries.map((e) => e.outDir).sort()) if (!outDirs.some((dir) => outDir.startsWith(dir))) outDirs.push(outDir);
1970
- for (const entry of entries) await (entry.type === "bundle" ? rolldownBuild(ctx, entry, hooks, config) : transformDir(ctx, entry));
2037
+ await Promise.all(entries.map((entry) => entry.type === "bundle" ? rolldownBuild(ctx, entry, hooks, config) : transformDir(ctx, entry)));
1971
2038
  await hooks.end?.(ctx);
1972
2039
  if (config.exports?.enabled) {
1973
2040
  const packageExports = generatePackageExports({
@@ -0,0 +1,3 @@
1
+ import { n as performBuild, t as build } from "./build-9ZPSBYPy.mjs";
2
+
3
+ export { performBuild };
@@ -0,0 +1,7 @@
1
+ //#region src/config.ts
2
+ function defineConfig(config) {
3
+ return config;
4
+ }
5
+
6
+ //#endregion
7
+ export { defineConfig as t };
@@ -0,0 +1,247 @@
1
+ import path from "node:path";
2
+ import { readFile } from "node:fs/promises";
3
+ import { createHash } from "node:crypto";
4
+ import { Buffer } from "node:buffer";
5
+ import { exactRegex, id, include, or } from "rolldown/filter";
6
+
7
+ //#region node_modules/.pnpm/rolldown-plugin-wasm@0.2.1_rolldown@1.0.0-rc.5/node_modules/rolldown-plugin-wasm/dist/index.mjs
8
+ const HELPERS_ID = "\0wasm-helpers.js";
9
+ const nodeFilePath = `
10
+ const { readFile } = process.getBuiltinModule('fs/promises')
11
+ const path = process.getBuiltinModule('path')
12
+
13
+ return readFile(path.resolve(import.meta.dirname, filepath)).then(
14
+ (buffer) => instantiate(buffer, imports)
15
+ )
16
+ `;
17
+ const nodeDecode = `
18
+ const { Buffer } = process.getBuiltinModule('buffer')
19
+ buf = Buffer.from(src, 'base64')
20
+ `;
21
+ const browserFilePath = `
22
+ return instantiate(fetch(filepath), imports, true);
23
+ `;
24
+ const browserDecode = `
25
+ const raw = globalThis.atob(src)
26
+ const len = raw.length
27
+ buf = new Uint8Array(new ArrayBuffer(len))
28
+ for (let i = 0; i < len; i++) {
29
+ buf[i] = raw.charCodeAt(i)
30
+ }
31
+ `;
32
+ const autoModule = `
33
+ let buf = null
34
+ const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null
35
+
36
+ if (filepath && isNode) {
37
+ ${nodeFilePath}
38
+ } else if (filepath) {
39
+ ${browserFilePath}
40
+ }
41
+
42
+ if (isNode) {
43
+ ${nodeDecode}
44
+ } else {
45
+ ${browserDecode}
46
+ }
47
+ `;
48
+ const nodeModule = `
49
+ let buf = null
50
+ if (filepath) {
51
+ ${nodeFilePath}
52
+ }
53
+
54
+ ${nodeDecode}
55
+ `;
56
+ const browserModule = `
57
+ let buf = null
58
+ if (filepath) {
59
+ ${browserFilePath}
60
+ }
61
+
62
+ ${browserDecode}
63
+ `;
64
+ const autoInlineModule = `
65
+ let buf = null
66
+ const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null
67
+ if (isNode) {
68
+ ${nodeDecode}
69
+ } else {
70
+ ${browserDecode}
71
+ }
72
+ `;
73
+ function envModule(env) {
74
+ switch (env) {
75
+ case "auto": return autoModule;
76
+ case "auto-inline": return autoInlineModule;
77
+ case "browser": return browserModule;
78
+ case "node": return nodeModule;
79
+ default: return null;
80
+ }
81
+ }
82
+ const getHelpersModule = (env) => `
83
+ export function loadWasmModule(sync, filepath, src, imports) {
84
+ function instantiate(source, imports, stream) {
85
+ const instantiate = stream ? WebAssembly.instantiateStreaming : WebAssembly.instantiate;
86
+ return instantiate(source, imports).then(({ instance }) => instance)
87
+ }
88
+
89
+ ${envModule(env)}
90
+
91
+ if (sync) {
92
+ const mod = new WebAssembly.Module(buf)
93
+ return new WebAssembly.Instance(mod, imports)
94
+ } else {
95
+ return instantiate(buf, imports)
96
+ }
97
+ }
98
+ `;
99
+ function codegenSimpleObject(obj) {
100
+ return `{ ${obj.map(({ key, value }) => {
101
+ return `${key}: ${typeof value === "string" ? value : codegenSimpleObject(value)}`;
102
+ }).join(",\n")} }`;
103
+ }
104
+ async function parseWasm(buffer) {
105
+ try {
106
+ const wasmModule = await WebAssembly.compile(buffer);
107
+ return {
108
+ imports: Object.entries(WebAssembly.Module.imports(wasmModule).reduce((result, item) => ({
109
+ ...result,
110
+ [item.module]: [...result[item.module] || [], item.name]
111
+ }), {})),
112
+ exports: WebAssembly.Module.exports(wasmModule).map((item) => item.name)
113
+ };
114
+ } catch (error) {
115
+ throw new Error(`Failed to parse WASM file: ${error.message}`, { cause: error });
116
+ }
117
+ }
118
+ function parseId(url) {
119
+ const [file, query] = url.split("?");
120
+ return [
121
+ file,
122
+ query,
123
+ new URLSearchParams(query)
124
+ ];
125
+ }
126
+ function wasm(options = {}) {
127
+ let { maxFileSize = 14 * 1024, publicPath = "", targetEnv, fileName = "[hash][extname]" } = options;
128
+ const copies = Object.create(null);
129
+ return {
130
+ name: "wasm",
131
+ buildStart(options$1) {
132
+ if (targetEnv == null) switch (options$1.platform) {
133
+ case "browser":
134
+ targetEnv = "browser";
135
+ break;
136
+ case "node":
137
+ targetEnv = "node";
138
+ break;
139
+ default: targetEnv = "auto";
140
+ }
141
+ },
142
+ resolveId: {
143
+ filter: [include(or(id(exactRegex(HELPERS_ID)), id(/\.wasm$/, { cleanUrl: true })))],
144
+ async handler(id$1, ...args) {
145
+ if (id$1 === HELPERS_ID) return id$1;
146
+ const [file, query] = parseId(id$1);
147
+ if (!query) return;
148
+ const resolved = await this.resolve(file, ...args);
149
+ if (resolved) resolved.id += `?${query}`;
150
+ return resolved;
151
+ }
152
+ },
153
+ load: {
154
+ filter: [include(or(id(exactRegex(HELPERS_ID)), id(/\.wasm$/, { cleanUrl: true })))],
155
+ async handler(id$1) {
156
+ if (id$1 === HELPERS_ID) return getHelpersModule(targetEnv || "auto");
157
+ const [file, , params] = parseId(id$1);
158
+ const buffer = await readFile(file);
159
+ const isInit = params.has("init");
160
+ const isSync = params.has("sync");
161
+ const isUrl = params.has("url");
162
+ if (isSync && isUrl) this.error("`sync` and `url` parameters cannot be used together.");
163
+ this.addWatchFile(file);
164
+ if (!isInit) this.getModuleInfo(id$1).meta.wasmInfo = await parseWasm(buffer);
165
+ function shouldInline() {
166
+ if (isUrl) return false;
167
+ if (isSync) return true;
168
+ if (targetEnv === "auto-inline") return true;
169
+ if (maxFileSize === 0) return false;
170
+ return buffer.length <= maxFileSize;
171
+ }
172
+ if (!shouldInline()) {
173
+ const hash = createHash("sha1").update(buffer).digest("hex").slice(0, 16);
174
+ const ext = path.extname(file);
175
+ const name = path.basename(file, ext);
176
+ const outputFileName = fileName.replaceAll("[hash]", hash).replaceAll("[extname]", ext).replaceAll("[name]", name);
177
+ copies[file] = {
178
+ filename: outputFileName,
179
+ publicFilepath: `${publicPath}${outputFileName}`,
180
+ buffer
181
+ };
182
+ }
183
+ return buffer.toString("binary");
184
+ }
185
+ },
186
+ transform: {
187
+ filter: [include(id(/\.wasm$/, { cleanUrl: true }))],
188
+ handler(code, id$1) {
189
+ const [file, , params] = parseId(id$1);
190
+ const publicFilepath = copies[file] ? JSON.stringify(copies[file].publicFilepath) : null;
191
+ let src;
192
+ const isSync = params.has("sync");
193
+ if (params.has("url")) {
194
+ if (!publicFilepath) this.error("`url` parameter can only be used with non-inlined files.");
195
+ return `export default ${publicFilepath}`;
196
+ }
197
+ if (publicFilepath === null) src = `'${Buffer.from(code, "binary").toString("base64")}'`;
198
+ else {
199
+ if (isSync) this.error("non-inlined files can not be `sync`.");
200
+ src = null;
201
+ }
202
+ const isInit = params.has("init");
203
+ let codegen = `import { loadWasmModule } from ${JSON.stringify(HELPERS_ID)}
204
+ ${isInit ? "export default " : ""}function __wasm_init(imports) {
205
+ return loadWasmModule(${isSync}, ${publicFilepath}, ${src}, imports)
206
+ }\n`;
207
+ const mod = this.getModuleInfo(id$1);
208
+ if (!isInit) {
209
+ const { imports, exports } = mod.meta.wasmInfo;
210
+ codegen += imports.map(([from], i) => {
211
+ return `import * as _wasmImport_${i} from ${JSON.stringify(from)}\n`;
212
+ });
213
+ const importObject = imports.map(([from, names], i) => {
214
+ return {
215
+ key: JSON.stringify(from),
216
+ value: names.map((name) => {
217
+ return {
218
+ key: JSON.stringify(name),
219
+ value: `_wasmImport_${i}[${JSON.stringify(name)}]`
220
+ };
221
+ })
222
+ };
223
+ });
224
+ codegen += `const instance = await __wasm_init(${codegenSimpleObject(importObject)})\n`;
225
+ codegen += exports.map((name) => {
226
+ return `export ${name === "default" ? "default" : `const ${name} =`} instance.exports.${name}`;
227
+ }).join("\n");
228
+ }
229
+ return {
230
+ map: { mappings: "" },
231
+ code: codegen
232
+ };
233
+ }
234
+ },
235
+ generateBundle() {
236
+ for (const copy of Object.values(copies)) this.emitFile({
237
+ type: "asset",
238
+ source: copy.buffer,
239
+ name: "Rolldown WASM Asset",
240
+ fileName: copy.filename
241
+ });
242
+ }
243
+ };
244
+ }
245
+
246
+ //#endregion
247
+ export { wasm };
@@ -2,7 +2,7 @@
2
2
  var package_default = {
3
3
  name: "robuild",
4
4
  type: "module",
5
- version: "0.1.5",
5
+ version: "0.1.7",
6
6
  packageManager: "pnpm@10.11.1",
7
7
  description: "Zero-config ESM/TS package builder. Powered by Rolldown and Oxc",
8
8
  license: "MIT",
@@ -33,43 +33,48 @@ var package_default = {
33
33
  "test:coverage:raw": "vitest run --coverage && node scripts/update-coverage.mjs",
34
34
  "test:coverage:watch": "vitest --coverage",
35
35
  "test:ui": "vitest --ui",
36
- "test:types": "tsc --noEmit --skipLibCheck src/**/*.ts",
36
+ "test:types": "tsc --noEmit",
37
37
  "docs:dev": "vitepress dev docs",
38
38
  "docs:build": "turbo docs:build:raw --filter=robuild",
39
39
  "docs:build:raw": "vitepress build docs",
40
40
  "docs:preview": "vitepress preview docs",
41
41
  "commit": "git-cz",
42
- "clean": "rm -rf .turbo dist coverage"
42
+ "clean": "rm -rf .turbo dist coverage",
43
+ "prepare": "husky"
43
44
  },
44
45
  dependencies: {
45
- "c12": "4.0.0-beta.2",
46
+ "c12": "4.0.0-beta.3",
46
47
  "cac": "^6.7.14",
48
+ "jiti": "^2.6.1",
47
49
  "chokidar": "^5.0.0",
48
50
  "consola": "^3.4.2",
49
51
  "exsolve": "^1.0.8",
50
- "glob": "^13.0.3",
52
+ "glob": "^13.0.6",
51
53
  "js-yaml": "^4.1.1",
52
54
  "magic-string": "^0.30.21",
53
- "minimatch": "^10.2.0",
54
- "oxc-minify": "^0.112.0",
55
- "oxc-parser": "^0.112.0",
56
- "oxc-transform": "^0.112.0",
55
+ "minimatch": "^10.2.2",
56
+ "oxc-minify": "^0.114.0",
57
+ "oxc-parser": "^0.114.0",
58
+ "oxc-transform": "^0.114.0",
57
59
  "pretty-bytes": "^7.1.0",
58
- "rolldown": "1.0.0-rc.4",
60
+ "rolldown": "1.0.0-rc.5",
59
61
  "rolldown-plugin-dts": "^0.22.1",
60
62
  "tinyglobby": "^0.2.15",
61
63
  "typescript": "^5.9.3"
62
64
  },
63
65
  devDependencies: {
64
66
  "@types/js-yaml": "^4.0.9",
65
- "@types/node": "^25.2.3",
67
+ "@types/node": "^25.3.0",
66
68
  "@vitest/coverage-v8": "^4.0.18",
67
69
  "automd": "^0.4.3",
68
70
  "changelogen": "^0.6.2",
69
71
  "esno": "^4.8.0",
70
72
  "git-cz": "^4.9.0",
73
+ "husky": "^9.1.7",
74
+ "lint-staged": "^16.2.7",
71
75
  "oxlint": "^1.49.0",
72
- "turbo": "^2.8.7",
76
+ "rolldown-plugin-wasm": "^0.2.1",
77
+ "turbo": "^2.8.10",
73
78
  "vitepress": "^1.6.4",
74
79
  "vitepress-plugin-group-icons": "^1.7.1",
75
80
  "vitest": "^4.0.18"
package/dist/cli.mjs CHANGED
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { g as logger, h as configureLogger, t as build } from "./_chunks/build-xQVEbj8V.mjs";
2
+ import { g as logger, h as configureLogger, t as build } from "./_chunks/build-9ZPSBYPy.mjs";
3
+ import module from "node:module";
3
4
  import { colors } from "consola/utils";
4
5
  import process from "node:process";
5
6
  import { loadConfig } from "c12";
6
7
  import { cac } from "cac";
7
8
 
8
9
  //#region src/cli.ts
10
+ try {
11
+ module.enableCompileCache?.();
12
+ } catch {}
9
13
  const pkg = await import("../package.json", { with: { type: "json" } });
10
14
  const cli = cac("robuild");
11
15
  cli.version(pkg.default.version).help();
package/dist/config.d.mts CHANGED
@@ -257,6 +257,15 @@ interface _BuildEntry {
257
257
  * @default false
258
258
  */
259
259
  unbundle?: boolean;
260
+ /**
261
+ * WASM support configuration for this entry.
262
+ *
263
+ * When enabled, you can import .wasm files directly.
264
+ * Overrides the global `wasm` config if set.
265
+ *
266
+ * @default false
267
+ */
268
+ wasm?: boolean | WasmOptions;
260
269
  }
261
270
  type BundleEntry = _BuildEntry & {
262
271
  type: 'bundle';
@@ -466,6 +475,60 @@ interface GlobImportOptions {
466
475
  */
467
476
  eager?: boolean;
468
477
  }
478
+ /**
479
+ * WASM support configuration options.
480
+ *
481
+ * When enabled, robuild will use rolldown-plugin-wasm to handle .wasm file imports.
482
+ *
483
+ * @see https://github.com/sxzz/rolldown-plugin-wasm
484
+ */
485
+ interface WasmOptions {
486
+ /**
487
+ * Enable WASM support.
488
+ *
489
+ * @default false
490
+ */
491
+ enabled?: boolean;
492
+ /**
493
+ * Maximum file size (in bytes) for inlining WASM modules.
494
+ *
495
+ * Files larger than this will be emitted as separate files.
496
+ * Set to 0 to always emit as separate files.
497
+ *
498
+ * @default 14336 (14KB)
499
+ */
500
+ maxFileSize?: number;
501
+ /**
502
+ * Output filename pattern for WASM files.
503
+ *
504
+ * Supports placeholders:
505
+ * - `[hash]` - Content hash
506
+ * - `[name]` - Original filename without extension
507
+ * - `[extname]` - File extension (e.g., `.wasm`)
508
+ *
509
+ * @default '[hash][extname]'
510
+ */
511
+ fileName?: string;
512
+ /**
513
+ * Public path prefix for WASM files.
514
+ *
515
+ * Used when WASM files need to be loaded from a specific URL path.
516
+ *
517
+ * @example '/assets/'
518
+ */
519
+ publicPath?: string;
520
+ /**
521
+ * Target environment for WASM instantiation.
522
+ *
523
+ * - `'auto'` - Detect environment at runtime (works in both Node.js and browser)
524
+ * - `'auto-inline'` - Always inline, decode based on environment
525
+ * - `'browser'` - Browser-only (omits Node.js built-in modules)
526
+ * - `'node'` - Node.js-only (requires Node.js 20.16.0+)
527
+ *
528
+ * @default 'auto'
529
+ */
530
+ targetEnv?: 'auto' | 'auto-inline' | 'browser' | 'node';
531
+ }
469
532
  interface BuildHooks {
470
533
  /**
471
534
  * Called at the start of the build process
@@ -699,6 +762,28 @@ interface BuildConfig {
699
762
  * Glob import configuration
700
763
  */
701
764
  globImport?: GlobImportOptions;
765
+ /**
766
+ * WASM support configuration.
767
+ *
768
+ * When enabled, you can import .wasm files directly in your code.
769
+ *
770
+ * @example
771
+ * ```ts
772
+ * // Direct import (sync)
773
+ * import { add } from './math.wasm'
774
+ *
775
+ * // Async initialization
776
+ * import init from './math.wasm?init'
777
+ * const instance = await init()
778
+ *
779
+ * // Sync initialization
780
+ * import initSync from './math.wasm?init&sync'
781
+ * const instance = initSync()
782
+ * ```
783
+ *
784
+ * @default false
785
+ */
786
+ wasm?: boolean | WasmOptions;
702
787
  /**
703
788
  * Log level for build output.
704
789
  *
package/dist/config.mjs CHANGED
@@ -1,7 +1,3 @@
1
- //#region src/config.ts
2
- function defineConfig(config) {
3
- return config;
4
- }
1
+ import { t as defineConfig } from "./_chunks/config-BlC5U5aX.mjs";
5
2
 
6
- //#endregion
7
3
  export { defineConfig };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { a as createBrowserShimsPlugin, c as SHEBANG_RE, d as shebangPlugin, f as nodeProtocolPlugin, g as logger, i as DEFAULT_SHIMS_CONFIG, l as hasShebang, m as hasGlobImports, o as createNodeShimsPlugin, p as createGlobImportPlugin, r as createSkipNodeModulesPlugin, s as createShimsPlugin, t as build, u as makeExecutable } from "./_chunks/build-xQVEbj8V.mjs";
1
+ import { a as createBrowserShimsPlugin, c as SHEBANG_RE, d as shebangPlugin, f as nodeProtocolPlugin, g as logger, i as DEFAULT_SHIMS_CONFIG, l as hasShebang, m as hasGlobImports, o as createNodeShimsPlugin, p as createGlobImportPlugin, r as createSkipNodeModulesPlugin, s as createShimsPlugin, t as build, u as makeExecutable } from "./_chunks/build-9ZPSBYPy.mjs";
2
2
  import { t as RobuildPluginManager } from "./_chunks/manager-uQxDLzY6.mjs";
3
- import { defineConfig } from "./config.mjs";
3
+ import { t as defineConfig } from "./_chunks/config-BlC5U5aX.mjs";
4
4
  import { extname } from "node:path";
5
5
  import { readFile } from "node:fs/promises";
6
6
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "robuild",
3
3
  "type": "module",
4
- "version": "0.1.5",
4
+ "version": "0.1.7",
5
5
  "packageManager": "pnpm@10.11.1",
6
6
  "description": "Zero-config ESM/TS package builder. Powered by Rolldown and Oxc",
7
7
  "license": "MIT",
@@ -34,43 +34,48 @@
34
34
  "test:coverage:raw": "vitest run --coverage && node scripts/update-coverage.mjs",
35
35
  "test:coverage:watch": "vitest --coverage",
36
36
  "test:ui": "vitest --ui",
37
- "test:types": "tsc --noEmit --skipLibCheck src/**/*.ts",
37
+ "test:types": "tsc --noEmit",
38
38
  "docs:dev": "vitepress dev docs",
39
39
  "docs:build": "turbo docs:build:raw --filter=robuild",
40
40
  "docs:build:raw": "vitepress build docs",
41
41
  "docs:preview": "vitepress preview docs",
42
42
  "commit": "git-cz",
43
- "clean": "rm -rf .turbo dist coverage"
43
+ "clean": "rm -rf .turbo dist coverage",
44
+ "prepare": "husky"
44
45
  },
45
46
  "dependencies": {
46
- "c12": "4.0.0-beta.2",
47
+ "c12": "4.0.0-beta.3",
47
48
  "cac": "^6.7.14",
49
+ "jiti": "^2.6.1",
48
50
  "chokidar": "^5.0.0",
49
51
  "consola": "^3.4.2",
50
52
  "exsolve": "^1.0.8",
51
- "glob": "^13.0.3",
53
+ "glob": "^13.0.6",
52
54
  "js-yaml": "^4.1.1",
53
55
  "magic-string": "^0.30.21",
54
- "minimatch": "^10.2.0",
55
- "oxc-minify": "^0.112.0",
56
- "oxc-parser": "^0.112.0",
57
- "oxc-transform": "^0.112.0",
56
+ "minimatch": "^10.2.2",
57
+ "oxc-minify": "^0.114.0",
58
+ "oxc-parser": "^0.114.0",
59
+ "oxc-transform": "^0.114.0",
58
60
  "pretty-bytes": "^7.1.0",
59
- "rolldown": "1.0.0-rc.4",
61
+ "rolldown": "1.0.0-rc.5",
60
62
  "rolldown-plugin-dts": "^0.22.1",
61
63
  "tinyglobby": "^0.2.15",
62
64
  "typescript": "^5.9.3"
63
65
  },
64
66
  "devDependencies": {
65
67
  "@types/js-yaml": "^4.0.9",
66
- "@types/node": "^25.2.3",
68
+ "@types/node": "^25.3.0",
67
69
  "@vitest/coverage-v8": "^4.0.18",
68
70
  "automd": "^0.4.3",
69
71
  "changelogen": "^0.6.2",
70
72
  "esno": "^4.8.0",
71
73
  "git-cz": "^4.9.0",
74
+ "husky": "^9.1.7",
75
+ "lint-staged": "^16.2.7",
72
76
  "oxlint": "^1.49.0",
73
- "turbo": "^2.8.7",
77
+ "rolldown-plugin-wasm": "^0.2.1",
78
+ "turbo": "^2.8.10",
74
79
  "vitepress": "^1.6.4",
75
80
  "vitepress-plugin-group-icons": "^1.7.1",
76
81
  "vitest": "^4.0.18"
@@ -1,3 +0,0 @@
1
- import { n as performBuild, t as build } from "./build-xQVEbj8V.mjs";
2
-
3
- export { performBuild };