rollipop 1.0.0-alpha.21 → 1.0.0-alpha.22

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/dist/index.js CHANGED
@@ -24,7 +24,6 @@ import { createDevMiddleware } from "@react-native/dev-middleware";
24
24
  import Fastify from "fastify";
25
25
  import mitt from "mitt";
26
26
  import http from "node:http";
27
- import EventEmitter from "node:events";
28
27
  import stripAnsi from "strip-ansi";
29
28
  import { SourceMapConsumer } from "source-map";
30
29
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -36,6 +35,7 @@ import { asConst } from "json-schema-to-ts";
36
35
  import mime from "mime";
37
36
  import Ajv from "ajv";
38
37
  import { codeFrameColumns } from "@babel/code-frame";
38
+ import EventEmitter from "node:events";
39
39
  import * as ws from "ws";
40
40
  import * as babel from "@babel/core";
41
41
  import * as swc from "@swc/core";
@@ -105,7 +105,7 @@ var constants_exports = /* @__PURE__ */ __exportAll({
105
105
  IMAGE_EXTENSIONS: () => IMAGE_EXTENSIONS,
106
106
  ROLLIPOP_VERSION: () => ROLLIPOP_VERSION
107
107
  });
108
- const ROLLIPOP_VERSION = "1.0.0-alpha.21";
108
+ const ROLLIPOP_VERSION = "1.0.0-alpha.22";
109
109
  const GLOBAL_IDENTIFIER = "global";
110
110
  /**
111
111
  * @see {@link https://github.com/facebook/metro/blob/0.81.x/docs/Configuration.md#resolvermainfields}
@@ -639,6 +639,8 @@ var ProgressBarStatusReporter = class {
639
639
  update(event) {
640
640
  switch (event.type) {
641
641
  case "bundle_build_started":
642
+ this.progressBar.setCurrent(0);
643
+ if (this.flags & ProgressFlags.FILE_CHANGED) this.progressBar.setTotal(0);
642
644
  this.flags |= ProgressFlags.BUILD_IN_PROGRESS;
643
645
  this.progressBar.start();
644
646
  this.renderManager.start();
@@ -655,10 +657,6 @@ var ProgressBarStatusReporter = class {
655
657
  break;
656
658
  case "transform":
657
659
  const { id, totalModules, transformedModules } = event;
658
- if (this.flags & ProgressFlags.FILE_CHANGED) {
659
- logger$2.debug("Transformed changed file", { id });
660
- return;
661
- }
662
660
  this.renderProgress(id, totalModules, transformedModules);
663
661
  break;
664
662
  case "watch_change":
@@ -809,6 +807,7 @@ function mergeConfig(baseConfig, ...overrideConfigs) {
809
807
  //#endregion
810
808
  //#region src/config/load-config.ts
811
809
  const CONFIG_FILE_NAME = "rollipop";
810
+ const INTERNAL_PLUGIN_HOOKS = ["transformCacheHit"];
812
811
  async function loadConfig(options = {}) {
813
812
  const { cwd = process.cwd(), configFile, mode, context = {} } = options;
814
813
  const defaultConfig = await getDefaultConfig(cwd, mode);
@@ -843,7 +842,12 @@ async function flattenPluginOption(pluginOption) {
843
842
  const awaitedPluginOption = await pluginOption;
844
843
  if (Array.isArray(awaitedPluginOption)) return (await Promise.all(awaitedPluginOption.map(flattenPluginOption))).flat();
845
844
  if (awaitedPluginOption == null || awaitedPluginOption === false) return [];
846
- return [awaitedPluginOption];
845
+ return [stripInternalPluginHooks(awaitedPluginOption)];
846
+ }
847
+ function stripInternalPluginHooks(plugin) {
848
+ const maybeInternalPlugin = plugin;
849
+ if (!INTERNAL_PLUGIN_HOOKS.some((hook) => hook in maybeInternalPlugin)) return plugin;
850
+ return omit(maybeInternalPlugin, INTERNAL_PLUGIN_HOOKS);
847
851
  }
848
852
  async function resolvePluginConfig(baseConfig, plugins) {
849
853
  let mergedConfig = omit(baseConfig, ["plugins"]);
@@ -1043,30 +1047,14 @@ function iife(body, path = "<unknown>") {
1043
1047
  }
1044
1048
  //#endregion
1045
1049
  //#region src/utils/config.ts
1046
- function bindReporter(config, eventSource, onEvent) {
1047
- const originalReporter = config.reporter;
1048
- config.reporter = { update(event) {
1049
- switch (event.type) {
1050
- case "bundle_build_started":
1051
- eventSource.emit("buildStart");
1052
- break;
1053
- case "bundle_build_done":
1054
- eventSource.emit("buildDone");
1055
- break;
1056
- case "bundle_build_failed":
1057
- eventSource.emit("buildFailed", event.error);
1058
- break;
1059
- case "transform":
1060
- eventSource.emit("transform", event.id, event.totalModules, event.transformedModules);
1061
- break;
1062
- case "watch_change":
1063
- eventSource.emit("watchChange", event.id);
1064
- break;
1065
- }
1066
- originalReporter?.update(event);
1050
+ function bindReporter(config, onEvent) {
1051
+ const reporter = { update(event) {
1067
1052
  onEvent?.(event);
1068
1053
  } };
1069
- return config;
1054
+ return {
1055
+ ...config,
1056
+ reporter
1057
+ };
1070
1058
  }
1071
1059
  function resolveHmrConfig(config) {
1072
1060
  if (config.mode !== "development") return null;
@@ -1298,7 +1286,7 @@ var FileSystemBundleStore = class {
1298
1286
  };
1299
1287
  //#endregion
1300
1288
  //#region src/server/bundler-pool.ts
1301
- var BundlerDevEngine = class extends EventEmitter {
1289
+ var BundlerDevEngine = class {
1302
1290
  initializeHandle;
1303
1291
  isHmrEnabled;
1304
1292
  _id;
@@ -1307,24 +1295,14 @@ var BundlerDevEngine = class extends EventEmitter {
1307
1295
  _devEngine = null;
1308
1296
  _state = "idle";
1309
1297
  _status = "idle";
1310
- constructor(options, config, buildOptions, onReporterEvent) {
1311
- super();
1298
+ constructor(options, config, buildOptions, eventBus) {
1312
1299
  this.options = options;
1313
1300
  this.config = config;
1314
1301
  this.buildOptions = buildOptions;
1315
- this.onReporterEvent = onReporterEvent;
1302
+ this.eventBus = eventBus;
1316
1303
  this._id = Bundler.createId(config, buildOptions);
1317
1304
  this.initializeHandle = taskHandler();
1318
1305
  this.isHmrEnabled = Boolean(buildOptions.dev && config.devMode.hmr);
1319
- this.on("buildStart", () => {
1320
- this._status = "building";
1321
- });
1322
- this.on("buildDone", () => {
1323
- this._status = "build-done";
1324
- });
1325
- this.on("buildFailed", () => {
1326
- this._status = "build-failed";
1327
- });
1328
1306
  this.initialize();
1329
1307
  }
1330
1308
  get id() {
@@ -1344,8 +1322,24 @@ var BundlerDevEngine = class extends EventEmitter {
1344
1322
  async initialize() {
1345
1323
  if (this._state !== "idle" || this._devEngine != null) return this;
1346
1324
  this._state = "initializing";
1347
- const onEvent = this.onReporterEvent ? (event) => this.onReporterEvent(this._id, event) : void 0;
1348
- const devEngine = await Bundler.devEngine(bindReporter(this.config, this, onEvent), this.buildOptions, {
1325
+ const config = bindReporter(this.config, (event) => {
1326
+ switch (event.type) {
1327
+ case "bundle_build_started":
1328
+ this._status = "building";
1329
+ break;
1330
+ case "bundle_build_done":
1331
+ this._status = "build-done";
1332
+ break;
1333
+ case "bundle_build_failed":
1334
+ this._status = "build-failed";
1335
+ break;
1336
+ }
1337
+ this.eventBus.emit({
1338
+ ...event,
1339
+ bundlerId: this.id
1340
+ });
1341
+ });
1342
+ const devEngine = await Bundler.devEngine(config, this.buildOptions, {
1349
1343
  host: this.options.server.host,
1350
1344
  port: this.options.server.port,
1351
1345
  onHmrUpdates: (errorOrResult) => {
@@ -1355,17 +1349,25 @@ var BundlerDevEngine = class extends EventEmitter {
1355
1349
  bundlerId: this.id,
1356
1350
  error: errorOrResult
1357
1351
  });
1358
- const normalizedError = normalizeRolldownError(errorOrResult);
1359
- this.config.reporter?.update({
1352
+ const event = {
1360
1353
  type: "bundle_build_failed",
1361
- error: normalizedError
1354
+ error: normalizeRolldownError(errorOrResult)
1355
+ };
1356
+ this._status = "build-failed";
1357
+ this.eventBus.emit({
1358
+ ...event,
1359
+ bundlerId: this.id
1362
1360
  });
1363
1361
  } else {
1364
1362
  logger$1.trace("Detected changed files", {
1365
1363
  bundlerId: this.id,
1366
1364
  changedFiles: errorOrResult.changedFiles
1367
1365
  });
1368
- this.emit("hmrUpdates", errorOrResult.updates);
1366
+ this.eventBus.emit({
1367
+ bundlerId: this.id,
1368
+ type: "hmr_updates",
1369
+ updates: errorOrResult.updates
1370
+ });
1369
1371
  }
1370
1372
  },
1371
1373
  onOutput: (errorOrResult) => {
@@ -1374,7 +1376,15 @@ var BundlerDevEngine = class extends EventEmitter {
1374
1376
  logger$1.trace("onOutput", { bundlerId: this.id });
1375
1377
  logger$1.error(errorOrResult.message);
1376
1378
  this.buildFailedError = normalizedError;
1377
- this.emit("buildFailed", normalizedError);
1379
+ const event = {
1380
+ type: "bundle_build_failed",
1381
+ error: normalizedError
1382
+ };
1383
+ this._status = "build-failed";
1384
+ this.eventBus.emit({
1385
+ ...event,
1386
+ bundlerId: this.id
1387
+ });
1378
1388
  } else {
1379
1389
  const output = errorOrResult.output[0];
1380
1390
  this.updateBundleStore(output);
@@ -1410,16 +1420,13 @@ var BundlerDevEngine = class extends EventEmitter {
1410
1420
  };
1411
1421
  var BundlerPool = class BundlerPool {
1412
1422
  static instances = /* @__PURE__ */ new Map();
1413
- constructor(config, resolvedServerOptions, onReporterEvent) {
1423
+ constructor(config, resolvedServerOptions, eventBus) {
1414
1424
  this.config = config;
1415
1425
  this.resolvedServerOptions = resolvedServerOptions;
1416
- this.onReporterEvent = onReporterEvent;
1417
- }
1418
- instanceKey(bundleName, buildOptions) {
1419
- return `${bundleName}-${Bundler.createId(this.config, buildOptions)}`;
1426
+ this.eventBus = eventBus;
1420
1427
  }
1421
1428
  get(bundleName, buildOptions) {
1422
- const key = this.instanceKey(getBaseBundleName(bundleName), buildOptions);
1429
+ const key = `${getBaseBundleName(bundleName)}-${Bundler.createId(this.config, buildOptions)}`;
1423
1430
  const instance = BundlerPool.instances.get(key);
1424
1431
  if (instance) return instance;
1425
1432
  else {
@@ -1427,16 +1434,14 @@ var BundlerPool = class BundlerPool {
1427
1434
  bundleName,
1428
1435
  key
1429
1436
  });
1430
- const instance = new BundlerDevEngine({ server: this.resolvedServerOptions }, this.config, buildOptions, this.onReporterEvent);
1437
+ const instance = new BundlerDevEngine({ server: this.resolvedServerOptions }, this.config, buildOptions, this.eventBus);
1431
1438
  logger$1.debug("Setting new bundler instance", { key });
1432
1439
  BundlerPool.instances.set(key, instance);
1433
1440
  return instance;
1434
1441
  }
1435
1442
  }
1436
1443
  /**
1437
- * Look up a cached bundler by its reporter-facing id (the same id carried
1438
- * in SSE events such as `bundle_build_done`). Returns `undefined` when no
1439
- * instance with that id has been created yet.
1444
+ * Look up a cached bundler by the id carried as `bundlerId` in events such as `bundle_build_done`. Returns `undefined` when no instance with that id has been created yet.
1440
1445
  */
1441
1446
  getInstanceById(id) {
1442
1447
  for (const instance of BundlerPool.instances.values()) if (instance.id === id) return instance;
@@ -1455,6 +1460,82 @@ function errorHandler(error, request, reply) {
1455
1460
  reply.status(500).send("Internal Server Error");
1456
1461
  }
1457
1462
  //#endregion
1463
+ //#region src/server/events/event-bus.ts
1464
+ var EventBus = class {
1465
+ listeners = /* @__PURE__ */ new Set();
1466
+ emit(event) {
1467
+ for (const listener of this.listeners) listener(event);
1468
+ }
1469
+ subscribe(listener) {
1470
+ this.listeners.add(listener);
1471
+ return () => {
1472
+ this.listeners.delete(listener);
1473
+ };
1474
+ }
1475
+ };
1476
+ var ServerEventBus = class extends EventBus {};
1477
+ //#endregion
1478
+ //#region src/server/sse/adapter.ts
1479
+ function toSSEEvent(event) {
1480
+ switch (event.type) {
1481
+ case "client_log": return {
1482
+ type: "client_log",
1483
+ ...event.bundlerId != null ? { bundlerId: event.bundlerId } : {},
1484
+ data: event.data
1485
+ };
1486
+ case "device_connected": return {
1487
+ type: "device_connected",
1488
+ clientId: event.client.id
1489
+ };
1490
+ case "device_disconnected": return {
1491
+ type: "device_disconnected",
1492
+ clientId: event.client.id
1493
+ };
1494
+ case "server_ready":
1495
+ case "cache_reset": return event;
1496
+ case "bundle_build_started":
1497
+ case "bundle_build_done":
1498
+ case "bundle_build_failed":
1499
+ case "watch_change": return reporterEventToSSEEvent(event);
1500
+ case "hmr_updates":
1501
+ case "device_message":
1502
+ case "device_error":
1503
+ case "transform": return null;
1504
+ }
1505
+ }
1506
+ function reporterEventToSSEEvent(event) {
1507
+ switch (event.type) {
1508
+ case "bundle_build_started": return {
1509
+ type: "bundle_build_started",
1510
+ bundlerId: event.bundlerId
1511
+ };
1512
+ case "bundle_build_done": return {
1513
+ type: "bundle_build_done",
1514
+ bundlerId: event.bundlerId,
1515
+ totalModules: event.totalModules,
1516
+ transformedModules: event.transformedModules,
1517
+ cacheHitModules: event.cacheHitModules,
1518
+ duration: event.duration
1519
+ };
1520
+ case "bundle_build_failed": return {
1521
+ type: "bundle_build_failed",
1522
+ bundlerId: event.bundlerId,
1523
+ error: stripAnsi(event.error.message)
1524
+ };
1525
+ case "watch_change": return {
1526
+ type: "watch_change",
1527
+ bundlerId: event.bundlerId,
1528
+ file: event.id
1529
+ };
1530
+ case "client_log": return {
1531
+ type: "client_log",
1532
+ bundlerId: event.bundlerId,
1533
+ data: event.data
1534
+ };
1535
+ case "transform": return null;
1536
+ }
1537
+ }
1538
+ //#endregion
1458
1539
  //#region src/server/mcp/server.ts
1459
1540
  function createMcpServer(options) {
1460
1541
  const { projectRoot, eventBus } = options;
@@ -1475,11 +1556,14 @@ function createMcpServer(options) {
1475
1556
  });
1476
1557
  server.registerTool("get_build_events", {
1477
1558
  title: "Get Build Events",
1478
- description: "Subscribe to bundler events for a duration. Returns all events (build start/done/fail, watch changes, client logs, device connections) collected during the wait period.",
1559
+ description: "Subscribe to bundler events for a duration. Returns all events (build start/done/fail, watch changes, client logs, device connections) collected during the wait period. Bundler-scoped events include bundlerId.",
1479
1560
  inputSchema: { duration: z.number().min(1e3).max(6e4).default(1e4).describe("How long to listen for events in milliseconds (1000-60000, default 10000)") }
1480
1561
  }, async ({ duration }) => {
1481
1562
  const events = [];
1482
- const unsubscribe = eventBus.collect(events);
1563
+ const unsubscribe = eventBus.subscribe((event) => {
1564
+ const sseEvent = toSSEEvent(event);
1565
+ if (sseEvent != null) events.push(sseEvent);
1566
+ });
1483
1567
  await new Promise((resolve) => setTimeout(resolve, duration));
1484
1568
  unsubscribe();
1485
1569
  if (events.length === 0) return { content: [{
@@ -1736,6 +1820,11 @@ const bundleRequestSchema = asConst({
1736
1820
  });
1737
1821
  new Ajv().compile(bundleRequestSchema);
1738
1822
  //#endregion
1823
+ //#region src/server/events/types.ts
1824
+ function isBundlerEventForId(event, bundlerId) {
1825
+ return "bundlerId" in event && event.bundlerId === bundlerId;
1826
+ }
1827
+ //#endregion
1739
1828
  //#region src/server/middlewares/serve-bundle.ts
1740
1829
  const routeParamSchema = asConst({
1741
1830
  type: "object",
@@ -1747,7 +1836,7 @@ function withGetBundleErrorHandler(reply, task) {
1747
1836
  });
1748
1837
  }
1749
1838
  const plugin$2 = fp((fastify, options) => {
1750
- const { getBundler } = options;
1839
+ const { eventBus, getBundler } = options;
1751
1840
  const getBundleOptions = (buildOptions) => {
1752
1841
  return {
1753
1842
  platform: buildOptions.platform,
@@ -1771,11 +1860,10 @@ const plugin$2 = fp((fastify, options) => {
1771
1860
  const bundler = getBundler(params.name, buildOptions);
1772
1861
  if (accept?.includes("multipart/mixed") ?? false) {
1773
1862
  const bundleResponse = new BundleResponse(reply);
1774
- const transformHandler = (_id, totalModules = 0, transformedModules) => {
1775
- bundleResponse.writeBundleState(transformedModules, totalModules);
1776
- };
1777
- bundler.on("transform", transformHandler);
1778
- await bundler.getBundle().then((bundle) => bundleResponse.endWithBundle(bundle.code)).catch((error) => bundleResponse.endWithError(error)).finally(() => bundler.off("transform", transformHandler));
1863
+ const unsubscribe = eventBus.subscribe((event) => {
1864
+ if (isBundlerEventForId(event, bundler.id) && event.type === "transform") bundleResponse.writeBundleState(event.transformedModules, event.totalModules ?? 0);
1865
+ });
1866
+ await bundler.getBundle().then((bundle) => bundleResponse.endWithBundle(bundle.code)).catch((error) => bundleResponse.endWithError(error)).finally(unsubscribe);
1779
1867
  } else {
1780
1868
  this.log.debug(`client is not support multipart/mixed content: ${accept ?? "<empty>"}`);
1781
1869
  const code = (await withGetBundleErrorHandler(reply, bundler.getBundle())).code;
@@ -1803,7 +1891,7 @@ const plugin$2 = fp((fastify, options) => {
1803
1891
  }, { name: "serve-bundle" });
1804
1892
  //#endregion
1805
1893
  //#region src/server/middlewares/sse.ts
1806
- const plugin$1 = fp((fastify, { eventBus }) => {
1894
+ const plugin$1 = fp((fastify, { publisher }) => {
1807
1895
  fastify.get("/sse/events", (request, reply) => {
1808
1896
  const res = reply.raw;
1809
1897
  res.writeHead(200, {
@@ -1814,8 +1902,8 @@ const plugin$1 = fp((fastify, { eventBus }) => {
1814
1902
  });
1815
1903
  request.raw.socket.setNoDelay(true);
1816
1904
  res.write(":ok\n\n");
1817
- eventBus.addClient(res);
1818
- request.raw.on("close", () => eventBus.removeClient(res));
1905
+ publisher.addClient(res);
1906
+ request.raw.on("close", () => publisher.removeClient(res));
1819
1907
  });
1820
1908
  }, { name: "sse" });
1821
1909
  //#endregion
@@ -1991,14 +2079,12 @@ function printSymbolicateResult(rawStackFrame, symbolicateResult) {
1991
2079
  }
1992
2080
  //#endregion
1993
2081
  //#region src/server/sse/event-bus.ts
1994
- var SSEEventBus = class {
2082
+ var SSEEventPublisher = class {
1995
2083
  clients = /* @__PURE__ */ new Set();
1996
- listeners = /* @__PURE__ */ new Set();
1997
- emit(event) {
2084
+ publish(event) {
1998
2085
  const data = JSON.stringify(event);
1999
2086
  const message = `event: ${event.type}\ndata: ${data}\n\n`;
2000
2087
  for (const client of this.clients) if (!client.closed) client.write(message);
2001
- for (const listener of this.listeners) listener(event);
2002
2088
  }
2003
2089
  /**
2004
2090
  * Subscribe an SSE HTTP response client.
@@ -2012,51 +2098,11 @@ var SSEEventBus = class {
2012
2098
  removeClient(res) {
2013
2099
  this.clients.delete(res);
2014
2100
  }
2015
- /**
2016
- * Subscribe a listener that collects events into the given array.
2017
- * Returns an unsubscribe function.
2018
- */
2019
- collect(collector) {
2020
- const listener = (event) => collector.push(event);
2021
- this.listeners.add(listener);
2022
- return () => this.listeners.delete(listener);
2023
- }
2024
2101
  get clientCount() {
2025
2102
  return this.clients.size;
2026
2103
  }
2027
2104
  };
2028
2105
  //#endregion
2029
- //#region src/server/sse/reporter.ts
2030
- function toSSEEvent(id, event) {
2031
- switch (event.type) {
2032
- case "bundle_build_started": return {
2033
- type: "bundle_build_started",
2034
- id
2035
- };
2036
- case "bundle_build_done": return {
2037
- type: "bundle_build_done",
2038
- id,
2039
- totalModules: event.totalModules,
2040
- duration: event.duration
2041
- };
2042
- case "bundle_build_failed": return {
2043
- type: "bundle_build_failed",
2044
- id,
2045
- error: stripAnsi(event.error.message)
2046
- };
2047
- case "watch_change": return {
2048
- type: "watch_change",
2049
- id,
2050
- file: event.id
2051
- };
2052
- case "client_log": return {
2053
- type: "client_log",
2054
- data: event.data
2055
- };
2056
- case "transform": return null;
2057
- }
2058
- }
2059
- //#endregion
2060
2106
  //#region src/server/wss/server.ts
2061
2107
  var WebSocketServer = class extends EventEmitter {
2062
2108
  clientId = 0;
@@ -2125,13 +2171,13 @@ function getWebSocketUpgradeHandler(websocketEndpoints) {
2125
2171
  //#region src/server/wss/hmr-server.ts
2126
2172
  var HMRServer = class extends WebSocketServer {
2127
2173
  bundlerPool;
2128
- reportEvent;
2174
+ eventBus;
2129
2175
  instances = /* @__PURE__ */ new Map();
2130
2176
  bindings = /* @__PURE__ */ new Map();
2131
- constructor({ bundlerPool, reportEvent }) {
2177
+ constructor({ bundlerPool, eventBus }) {
2132
2178
  super("hmr", { noServer: true });
2133
2179
  this.bundlerPool = bundlerPool;
2134
- this.reportEvent = reportEvent;
2180
+ this.eventBus = eventBus;
2135
2181
  }
2136
2182
  parseClientMessage(data) {
2137
2183
  const parsedData = JSON.parse(this.rawDataToString(data));
@@ -2158,33 +2204,34 @@ var HMRServer = class extends WebSocketServer {
2158
2204
  }
2159
2205
  bindEvents(client, instance) {
2160
2206
  if (this.bindings.get(client.id) == null) {
2161
- const handleHmrUpdates = (updates) => {
2162
- this.handleUpdates(client, updates);
2163
- };
2164
- const handleWatchChange = () => {
2165
- this.send(client, JSON.stringify({ type: "hmr:update-start" }));
2166
- };
2167
- const handleBuildFailed = (error) => {
2168
- this.send(client, JSON.stringify({
2169
- type: "hmr:error",
2170
- payload: {
2171
- type: "BuildError",
2172
- errors: [{ description: error.message }],
2173
- message: error.message
2174
- }
2175
- }));
2176
- };
2177
- instance.addListener("hmrUpdates", handleHmrUpdates);
2178
- instance.addListener("watchChange", handleWatchChange);
2179
- instance.addListener("buildFailed", handleBuildFailed);
2180
- this.bindings.set(client.id, {
2181
- hmrUpdates: handleHmrUpdates,
2182
- watchChange: handleWatchChange,
2183
- buildFailed: handleBuildFailed
2207
+ const unsubscribe = this.eventBus.subscribe((event) => {
2208
+ if (!isBundlerEventForId(event, instance.id)) return;
2209
+ switch (event.type) {
2210
+ case "hmr_updates":
2211
+ this.handleUpdates(client, event.updates);
2212
+ break;
2213
+ case "watch_change":
2214
+ this.send(client, JSON.stringify({ type: "hmr:update-start" }));
2215
+ break;
2216
+ case "bundle_build_failed":
2217
+ this.sendBuildFailed(client, event.error);
2218
+ break;
2219
+ }
2184
2220
  });
2221
+ this.bindings.set(client.id, { unsubscribe });
2185
2222
  this.logger.trace(`HMR event binding established (clientId: ${client.id})`);
2186
2223
  }
2187
2224
  }
2225
+ sendBuildFailed(client, error) {
2226
+ this.send(client, JSON.stringify({
2227
+ type: "hmr:error",
2228
+ payload: {
2229
+ type: "BuildError",
2230
+ errors: [{ description: error.message }],
2231
+ message: error.message
2232
+ }
2233
+ }));
2234
+ }
2188
2235
  async handleModuleRegistered(client, modules) {
2189
2236
  try {
2190
2237
  const instance = this.instances.get(client.id);
@@ -2251,11 +2298,7 @@ var HMRServer = class extends WebSocketServer {
2251
2298
  this.logger.trace(`HMR client cleanup (clientId: ${client.id})`);
2252
2299
  const binding = this.bindings.get(client.id);
2253
2300
  const instance = this.instances.get(client.id);
2254
- if (binding != null && instance != null) {
2255
- instance.removeListener("hmrUpdates", binding.hmrUpdates);
2256
- instance.removeListener("watchChange", binding.watchChange);
2257
- instance.removeListener("buildFailed", binding.buildFailed);
2258
- }
2301
+ if (binding != null) binding.unsubscribe();
2259
2302
  if (instance != null) try {
2260
2303
  instance.devEngine.removeClient(String(client.id));
2261
2304
  } catch (error) {
@@ -2302,13 +2345,16 @@ var HMRServer = class extends WebSocketServer {
2302
2345
  case "hmr:invalidate":
2303
2346
  this.handleInvalidate(client, message.moduleId);
2304
2347
  break;
2305
- case "hmr:log":
2306
- this.reportEvent({
2348
+ case "hmr:log": {
2349
+ const instance = this.instances.get(client.id);
2350
+ this.eventBus.emit({
2307
2351
  type: "client_log",
2352
+ ...instance != null ? { bundlerId: instance.id } : {},
2308
2353
  level: message.level,
2309
2354
  data: message.data
2310
2355
  });
2311
2356
  break;
2357
+ }
2312
2358
  }
2313
2359
  }
2314
2360
  onConnection(client) {
@@ -2349,22 +2395,58 @@ async function createDevServer(config, options) {
2349
2395
  loggerInstance: new DevServerLogger(),
2350
2396
  disableRequestLogging: true
2351
2397
  });
2352
- const sseEventBus = new SSEEventBus();
2353
- const bundlerPool = new BundlerPool(config, {
2354
- host,
2355
- port
2356
- }, (id, event) => {
2357
- const sseEvent = toSSEEvent(id, event);
2358
- if (sseEvent) sseEventBus.emit(sseEvent);
2398
+ const eventBus = new ServerEventBus();
2399
+ const ssePublisher = new SSEEventPublisher();
2400
+ const reporter = config.reporter;
2401
+ eventBus.subscribe((event) => {
2402
+ const sseEvent = toSSEEvent(event);
2403
+ if (sseEvent != null) ssePublisher.publish(sseEvent);
2359
2404
  });
2360
- const getBundler = (bundleName, buildOptions) => {
2361
- return bundlerPool.get(bundleName, merge(options?.buildOptions ?? {}, buildOptions));
2362
- };
2363
2405
  const { middleware: communityMiddleware, websocketEndpoints: communityWebsocketEndpoints, messageSocketEndpoint: { server: messageServer, broadcast }, eventsSocketEndpoint: { server: eventsServer, reportEvent } } = createDevServerMiddleware({
2364
2406
  port,
2365
2407
  host,
2366
2408
  watchFolders: []
2367
2409
  });
2410
+ eventBus.subscribe((event) => {
2411
+ switch (event.type) {
2412
+ case "bundle_build_started":
2413
+ case "bundle_build_done":
2414
+ case "bundle_build_failed":
2415
+ case "transform":
2416
+ case "watch_change":
2417
+ reporter?.update(event);
2418
+ break;
2419
+ case "client_log":
2420
+ reportEvent?.(event);
2421
+ reporter?.update(event);
2422
+ break;
2423
+ case "device_connected":
2424
+ emitter.emit("device.connected", { client: event.client });
2425
+ break;
2426
+ case "device_message":
2427
+ emitter.emit("device.message", {
2428
+ client: event.client,
2429
+ data: event.data
2430
+ });
2431
+ break;
2432
+ case "device_error":
2433
+ emitter.emit("device.error", {
2434
+ client: event.client,
2435
+ error: event.error
2436
+ });
2437
+ break;
2438
+ case "device_disconnected":
2439
+ emitter.emit("device.disconnected", { client: event.client });
2440
+ break;
2441
+ }
2442
+ });
2443
+ const bundlerPool = new BundlerPool(config, {
2444
+ host,
2445
+ port
2446
+ }, eventBus);
2447
+ const getBundler = (bundleName, buildOptions) => {
2448
+ return bundlerPool.get(bundleName, merge(options?.buildOptions ?? {}, buildOptions));
2449
+ };
2368
2450
  const { middleware: devMiddleware, websocketEndpoints } = createDevMiddleware({
2369
2451
  serverBaseUrl,
2370
2452
  logger: {
@@ -2382,29 +2464,22 @@ async function createDevServer(config, options) {
2382
2464
  });
2383
2465
  const hmrServer = new HMRServer({
2384
2466
  bundlerPool,
2385
- reportEvent: (event) => {
2386
- reportEvent?.(event);
2387
- config.reporter?.update(event);
2388
- }
2389
- }).on("connection", (client) => {
2390
- emitter.emit("device.connected", { client });
2391
- sseEventBus.emit({
2392
- type: "device_connected",
2393
- clientId: client.id
2394
- });
2395
- }).on("message", (client, data) => emitter.emit("device.message", {
2467
+ eventBus
2468
+ }).on("connection", (client) => eventBus.emit({
2469
+ type: "device_connected",
2470
+ client
2471
+ })).on("message", (client, data) => eventBus.emit({
2472
+ type: "device_message",
2396
2473
  client,
2397
2474
  data
2398
- })).on("error", (client, error) => emitter.emit("device.error", {
2475
+ })).on("error", (client, error) => eventBus.emit({
2476
+ type: "device_error",
2399
2477
  client,
2400
2478
  error
2401
- })).on("close", (client) => {
2402
- emitter.emit("device.disconnected", { client });
2403
- sseEventBus.emit({
2404
- type: "device_disconnected",
2405
- clientId: client.id
2406
- });
2407
- });
2479
+ })).on("close", (client) => eventBus.emit({
2480
+ type: "device_disconnected",
2481
+ client
2482
+ }));
2408
2483
  await fastify.register(import("@fastify/middie"));
2409
2484
  const devServer = {
2410
2485
  ...emitter,
@@ -2429,13 +2504,16 @@ async function createDevServer(config, options) {
2429
2504
  })
2430
2505
  };
2431
2506
  const { invokePostConfigureServer } = await invokeConfigureServer(devServer, config.plugins ?? []);
2432
- fastify.use(requestLogger).use(communityMiddleware).use(devMiddleware).register(plugin$1, { eventBus: sseEventBus }).register(plugin$4, {
2507
+ fastify.use(requestLogger).use(communityMiddleware).use(devMiddleware).register(plugin$1, { publisher: ssePublisher }).register(plugin$4, {
2433
2508
  projectRoot,
2434
- eventBus: sseEventBus
2509
+ eventBus
2435
2510
  }).register(plugin$5, { bundlerPool }).register(plugin$6, {
2436
2511
  projectRoot,
2437
- eventBus: sseEventBus
2438
- }).register(plugin, { getBundler }).register(plugin$2, { getBundler }).register(plugin$3, {
2512
+ eventBus
2513
+ }).register(plugin, { getBundler }).register(plugin$2, {
2514
+ eventBus,
2515
+ getBundler
2516
+ }).register(plugin$3, {
2439
2517
  projectRoot,
2440
2518
  host,
2441
2519
  port,
@@ -2448,7 +2526,7 @@ async function createDevServer(config, options) {
2448
2526
  "/hot": hmrServer.server
2449
2527
  }));
2450
2528
  await invokePostConfigureServer();
2451
- sseEventBus.emit({
2529
+ eventBus.emit({
2452
2530
  type: "server_ready",
2453
2531
  host,
2454
2532
  port
@@ -3077,21 +3155,45 @@ function reporterPlugin(options) {
3077
3155
  let totalModules = initialTotalModules;
3078
3156
  let startedAt = 0;
3079
3157
  let transformedModules = 0;
3158
+ let cacheHitModules = 0;
3080
3159
  let unknownTotalModules = totalModules === 0;
3160
+ let rebuildPending = false;
3161
+ function getProcessedModules() {
3162
+ return transformedModules + cacheHitModules;
3163
+ }
3164
+ function reportProgress(id) {
3165
+ const processedModules = getProcessedModules();
3166
+ if (!unknownTotalModules && totalModules < processedModules) totalModules = processedModules;
3167
+ reporter?.update({
3168
+ type: "transform",
3169
+ id,
3170
+ totalModules: unknownTotalModules ? void 0 : totalModules,
3171
+ transformedModules: processedModules
3172
+ });
3173
+ }
3081
3174
  return {
3082
3175
  name: "rollipop:status",
3083
3176
  buildStart() {
3084
3177
  startedAt = performance.now();
3085
3178
  transformedModules = 0;
3179
+ cacheHitModules = 0;
3180
+ if (rebuildPending) {
3181
+ totalModules = 0;
3182
+ unknownTotalModules = false;
3183
+ rebuildPending = false;
3184
+ }
3086
3185
  reporter?.update({ type: "bundle_build_started" });
3087
3186
  },
3088
3187
  buildEnd(error) {
3089
3188
  const endedAt = performance.now();
3090
- if (transformedModules !== 0) totalModules = transformedModules;
3189
+ const processedModules = getProcessedModules();
3190
+ if (processedModules !== 0) totalModules = processedModules;
3091
3191
  unknownTotalModules = false;
3092
3192
  reporter?.update(error == null ? {
3093
3193
  type: "bundle_build_done",
3094
3194
  totalModules,
3195
+ transformedModules,
3196
+ cacheHitModules,
3095
3197
  duration: endedAt - startedAt
3096
3198
  } : {
3097
3199
  type: "bundle_build_failed",
@@ -3102,16 +3204,15 @@ function reporterPlugin(options) {
3102
3204
  order: "post",
3103
3205
  handler(_code, id) {
3104
3206
  ++transformedModules;
3105
- if (!unknownTotalModules && totalModules < transformedModules) totalModules = transformedModules;
3106
- reporter?.update({
3107
- type: "transform",
3108
- id,
3109
- totalModules: unknownTotalModules ? void 0 : totalModules,
3110
- transformedModules
3111
- });
3207
+ reportProgress(id);
3112
3208
  }
3113
3209
  },
3210
+ transformCacheHit(id) {
3211
+ ++cacheHitModules;
3212
+ reportProgress(id);
3213
+ },
3114
3214
  watchChange(id) {
3215
+ rebuildPending = true;
3115
3216
  reporter?.update({
3116
3217
  type: "watch_change",
3117
3218
  id
@@ -3480,7 +3581,7 @@ async function runServer(config, options) {
3480
3581
  }
3481
3582
  //#endregion
3482
3583
  //#region package.json
3483
- var version = "1.0.0-alpha.21";
3584
+ var version = "1.0.0-alpha.22";
3484
3585
  //#endregion
3485
3586
  //#region src/node/logger.ts
3486
3587
  const logger = new Logger("cli");
@@ -3742,6 +3843,7 @@ function getAgentGuide(baseUrl = defaultBaseUrl) {
3742
3843
  " Event format:",
3743
3844
  " event: <event_type>",
3744
3845
  " data: <json_payload>",
3846
+ " bundler-scoped events include bundlerId",
3745
3847
  "",
3746
3848
  " Useful event types:",
3747
3849
  " bundle_build_started, bundle_build_done, bundle_build_failed",
@@ -3809,6 +3911,7 @@ const action$1 = async function(options) {
3809
3911
  dev: options.dev,
3810
3912
  minify: options.minify,
3811
3913
  cache: options.cache,
3914
+ ...options.sourcemapOutput ? { sourcemap: true } : {},
3812
3915
  outfile: options.bundleOutput,
3813
3916
  sourcemapOutfile: options.sourcemapOutput,
3814
3917
  assetsDir: options.assetsDest