rollipop 1.0.0-alpha.32 → 1.0.0-alpha.34

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.
@@ -1,13 +1,40 @@
1
+ import * as swc from "@swc/core";
1
2
  import transform from "fast-flow-transform";
2
3
  //#region src/common/transformer.ts
3
4
  async function stripFlowTypes(id, code) {
4
- return await transform({
5
- filename: id,
6
- source: code,
7
- sourcemap: true,
8
- dialect: "flow",
9
- format: "pretty"
10
- });
5
+ try {
6
+ return await transform({
7
+ filename: id,
8
+ source: code,
9
+ sourcemap: true,
10
+ dialect: "flow",
11
+ format: "pretty"
12
+ });
13
+ } catch {
14
+ const result = swc.transformSync(code, {
15
+ filename: id,
16
+ configFile: false,
17
+ swcrc: false,
18
+ sourceMaps: true,
19
+ inputSourceMap: false,
20
+ jsc: {
21
+ target: "esnext",
22
+ parser: {
23
+ syntax: "flow",
24
+ all: true,
25
+ jsx: true,
26
+ enums: true,
27
+ components: true,
28
+ patternMatching: true
29
+ },
30
+ minify: { format: { comments: false } }
31
+ }
32
+ });
33
+ return {
34
+ code: result.code,
35
+ map: result.map ? JSON.parse(result.map) : void 0
36
+ };
37
+ }
11
38
  }
12
39
  //#endregion
13
40
  export { stripFlowTypes };
@@ -1,8 +1,8 @@
1
1
  import { stripFlowTypes } from "../common/transformer.js";
2
- import { DEFAULT_ANALYZE_FILE, DEFAULT_ANALYZE_REPORT_FILE, DEFAULT_ASSET_EXTENSIONS, DEFAULT_ASSET_REGISTRY_PATH, DEFAULT_ENV_FILE, DEFAULT_ENV_PREFIX, DEFAULT_HMR_CLIENT_PATH, DEFAULT_RESOLVER_ALIAS_FIELDS, DEFAULT_RESOLVER_CONDITION_NAMES, DEFAULT_RESOLVER_MAIN_FIELDS, DEFAULT_RUNTIME_TARGET, DEFAULT_SOURCE_EXTENSIONS } from "../constants.js";
2
+ import { DEFAULT_ANALYZE_FILE, DEFAULT_ANALYZE_REPORT_FILE, DEFAULT_ASSET_EXTENSIONS, DEFAULT_ENV_FILE, DEFAULT_ENV_PREFIX, DEFAULT_HMR_CLIENT_PATH, DEFAULT_RESOLVER_ALIAS_FIELDS, DEFAULT_RESOLVER_CONDITION_NAMES, DEFAULT_RESOLVER_MAIN_FIELDS, DEFAULT_RUNTIME_TARGET, DEFAULT_SOURCE_EXTENSIONS } from "../constants.js";
3
3
  import { ClientLogReporter } from "../events/builtin-reporters.js";
4
- import { getInitializeCorePath, getPolyfillScriptPaths } from "../internal/react-native.js";
5
4
  import { resolvePackagePath } from "../utils/node-resolve.js";
5
+ import { resolveAssetRegistryPath, resolveInitializeCorePath, resolvePolyfillScriptPaths } from "../internal/react-native.js";
6
6
  import fs from "node:fs";
7
7
  //#region src/config/defaults.ts
8
8
  async function getDefaultConfig(projectRoot, mode) {
@@ -26,8 +26,8 @@ async function getDefaultConfig(projectRoot, mode) {
26
26
  symlinks: true
27
27
  },
28
28
  transform: { swc: { native: { externalHelpers: true } } },
29
- prelude: [getInitializeCorePath(projectRoot)],
30
- polyfills: await Promise.all(getPolyfillScriptPaths(reactNativePath).map(async (path) => {
29
+ prelude: [resolveInitializeCorePath(projectRoot)],
30
+ polyfills: await Promise.all(resolvePolyfillScriptPaths(projectRoot, reactNativePath).map(async (path) => {
31
31
  const code = fs.readFileSync(path, "utf-8");
32
32
  return {
33
33
  type: "iife",
@@ -38,7 +38,7 @@ async function getDefaultConfig(projectRoot, mode) {
38
38
  treeshake: true,
39
39
  reactNative: {
40
40
  reactNativePath,
41
- assetRegistryPath: DEFAULT_ASSET_REGISTRY_PATH,
41
+ assetRegistryPath: resolveAssetRegistryPath(projectRoot),
42
42
  hmrClientPath: DEFAULT_HMR_CLIENT_PATH
43
43
  },
44
44
  dev: {
@@ -5,11 +5,11 @@ import "../core/plugins/index.js";
5
5
  import { InteractiveCommand } from "../node/commands/start/setup-interactive-mode.js";
6
6
  import "../node/cli-utils.js";
7
7
  import { Plugin } from "../core/plugins/types.js";
8
+ import * as swc from "@swc/core";
8
9
  import { DevWatchOptions, RollipopReactNativeFlowConfig, RollipopReactNativeSwcConfig, RollipopReactNativeWorkletsConfig } from "@rollipop/rolldown/experimental";
9
10
  import * as rolldown from "@rollipop/rolldown";
10
11
  import { TopLevelFilterExpression } from "@rollipop/rolldown/filter";
11
12
  import * as babel from "@babel/core";
12
- import * as swc from "@swc/core";
13
13
  //#region src/config/types.d.ts
14
14
  type RolldownExperimentalOptions = NonNullable<rolldown.InputOptions['experimental']>;
15
15
  type RolldownTransformOptions = NonNullable<rolldown.InputOptions['transform']>;
@@ -267,7 +267,8 @@ export interface ReactNativeConfig {
267
267
  /**
268
268
  * Path to asset registry file.
269
269
  *
270
- * Defaults to: `react-native/Libraries/Image/AssetRegistry.js`
270
+ * Defaults to: `react-native/asset-registry`,
271
+ * with the legacy `react-native/Libraries/Image/AssetRegistry.js` path as a fallback.
271
272
  */
272
273
  assetRegistryPath?: string | ((root: string) => MaybePromise<string>);
273
274
  /**
package/dist/constants.js CHANGED
@@ -20,7 +20,7 @@ var constants_exports = /* @__PURE__ */ __exportAll({
20
20
  ROLLIPOP_VIRTUAL_ENTRY_ID: () => ROLLIPOP_VIRTUAL_ENTRY_ID,
21
21
  ROLLIPOP_VIRTUAL_PREFIX: () => ROLLIPOP_VIRTUAL_PREFIX
22
22
  });
23
- const ROLLIPOP_VERSION = "1.0.0-alpha.32";
23
+ const ROLLIPOP_VERSION = "1.0.0-alpha.34";
24
24
  const ROLLIPOP_VIRTUAL_PREFIX = "\0rollipop/";
25
25
  const ROLLIPOP_VIRTUAL_BOOTSTRAP_ID = `${ROLLIPOP_VIRTUAL_PREFIX}bootstrap`;
26
26
  const ROLLIPOP_VIRTUAL_ENTRY_ID = `${ROLLIPOP_VIRTUAL_PREFIX}entry`;
@@ -5,6 +5,7 @@ import * as rolldown from "@rollipop/rolldown";
5
5
  export interface ReactNativePluginOptions {
6
6
  context: BundlerContext;
7
7
  projectRoot: string;
8
+ reactNativePath: string;
8
9
  platform: string;
9
10
  preferNativePlatform: boolean;
10
11
  buildType: BuildType;
@@ -1,10 +1,32 @@
1
1
  import { copyAssetsToDestination, generateAssetRegistryCode, resolveScaledAssets } from "../assets.js";
2
2
  import { setFlag } from "./utils/transform-utils.js";
3
+ import path from "node:path";
3
4
  import { rollipopReactNativePlugin } from "@rollipop/rolldown/experimental";
4
5
  import { id, include } from "@rollipop/rolldown/filter";
5
6
  //#region src/core/plugins/react-native-plugin.ts
6
7
  function reactNativePlugin(options) {
7
- const { projectRoot, platform, preferNativePlatform, buildType, context, assetsDir, assetExtensions, assetRegistryPath, builtinPluginConfig } = options;
8
+ const { projectRoot, reactNativePath, platform, preferNativePlatform, buildType, context, assetsDir, assetExtensions, assetRegistryPath, builtinPluginConfig } = options;
9
+ const privateModulePlugin = {
10
+ name: "rollipop:react-native-private-module",
11
+ resolveId: {
12
+ order: "post",
13
+ filter: [include(id(new RegExp(`^react-native/src/private/`)))],
14
+ async handler(source, importer, extraOptions) {
15
+ const resolved = await this.resolve(source, importer, {
16
+ ...extraOptions,
17
+ skipSelf: true
18
+ });
19
+ if (resolved != null) return resolved;
20
+ const candidate = path.resolve(reactNativePath, source.slice(13));
21
+ const relativeCandidate = path.relative(reactNativePath, candidate);
22
+ if (relativeCandidate.startsWith("..") || path.isAbsolute(relativeCandidate)) return null;
23
+ return this.resolve(candidate, importer, {
24
+ ...extraOptions,
25
+ skipSelf: true
26
+ });
27
+ }
28
+ }
29
+ };
8
30
  const assets = [];
9
31
  const assetPlugin = {
10
32
  name: "rollipop:react-native-asset",
@@ -42,7 +64,11 @@ function reactNativePlugin(options) {
42
64
  }
43
65
  }
44
66
  };
45
- return [rollipopReactNativePlugin(builtinPluginConfig), assetPlugin];
67
+ return [
68
+ privateModulePlugin,
69
+ rollipopReactNativePlugin(builtinPluginConfig),
70
+ assetPlugin
71
+ ];
46
72
  }
47
73
  //#endregion
48
74
  export { reactNativePlugin as reactNative };
@@ -1,9 +1,9 @@
1
1
  import { getFlag } from "./utils/transform-utils.js";
2
2
  import { ROLLDOWN_RUNTIME_EXCLUDE_FILTER, withRuntimeExclude } from "./shared/filters.js";
3
3
  import { mergeSwcOptions } from "../../utils/swc.js";
4
+ import * as swc from "@swc/core";
4
5
  import { partition } from "es-toolkit";
5
6
  import { id, include } from "@rollipop/rolldown/filter";
6
- import * as swc from "@swc/core";
7
7
  //#region src/core/plugins/swc-plugin.ts
8
8
  function swcPlugin({ context, transformConfig }) {
9
9
  const { rules = [] } = transformConfig ?? {};
@@ -2,8 +2,8 @@ import { ROLLIPOP_VIRTUAL_ENTRY_ID } from "../constants.js";
2
2
  import { isDebugEnabled } from "../common/env.js";
3
3
  import { CompatStatusReporter, ProgressBarStatusReporter } from "../events/builtin-reporters.js";
4
4
  import { asLiteral, iife, nodeEnvironment } from "../common/code.js";
5
- import { getGlobalVariables } from "../internal/react-native.js";
6
5
  import { resolveFrom, resolvePackageJson } from "../utils/node-resolve.js";
6
+ import { getGlobalVariables } from "../internal/react-native.js";
7
7
  import { printPluginLog } from "./plugins/context.js";
8
8
  import { applyRolldownOptionsConfig } from "../config/compose-override.js";
9
9
  import { createVirtualModuleId, escapeVirtualModuleId } from "../utils/id.js";
@@ -200,6 +200,7 @@ async function resolveReactNativePluginOptions(config, context, buildOptions) {
200
200
  return {
201
201
  context,
202
202
  projectRoot: config.root,
203
+ reactNativePath: config.reactNative.reactNativePath,
203
204
  platform: buildOptions.platform,
204
205
  preferNativePlatform: config.resolve.preferNativePlatform,
205
206
  buildType: context.buildType,
@@ -261,6 +261,14 @@ var __rolldown_runtime__ = (function() {
261
261
  } });
262
262
  if (t) _set_prototype_of(e, t);
263
263
  }
264
+ function _is_native_reflect_construct() {
265
+ try {
266
+ var e = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}));
267
+ } catch (e) {}
268
+ return (_is_native_reflect_construct = function() {
269
+ return !!e;
270
+ })();
271
+ }
264
272
  function _iterable_to_array(e) {
265
273
  if (typeof Symbol !== "undefined" && e[Symbol.iterator] != null || e["@@iterator"] != null) return Array.from(e);
266
274
  }
@@ -289,10 +297,10 @@ var __rolldown_runtime__ = (function() {
289
297
  return n;
290
298
  }
291
299
  function _non_iterable_rest() {
292
- throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
300
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
293
301
  }
294
302
  function _non_iterable_spread() {
295
- throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
303
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
296
304
  }
297
305
  function _possible_constructor_return(e, t) {
298
306
  if (t && (_type_of(t) === "object" || typeof t === "function")) return t;
@@ -311,26 +319,6 @@ var __rolldown_runtime__ = (function() {
311
319
  function _to_consumable_array(e) {
312
320
  return _array_without_holes(e) || _iterable_to_array(e) || _unsupported_iterable_to_array(e) || _non_iterable_spread();
313
321
  }
314
- function _type_of(e) {
315
- "@swc/helpers - typeof";
316
- return e && typeof Symbol !== "undefined" && e.constructor === Symbol ? "symbol" : typeof e;
317
- }
318
- function _unsupported_iterable_to_array(e, t) {
319
- if (!e) return;
320
- if (typeof e === "string") return _array_like_to_array(e, t);
321
- var r = Object.prototype.toString.call(e).slice(8, -1);
322
- if (r === "Object" && e.constructor) r = e.constructor.name;
323
- if (r === "Map" || r === "Set") return Array.from(r);
324
- if (r === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)) return _array_like_to_array(e, t);
325
- }
326
- function _is_native_reflect_construct() {
327
- try {
328
- var e = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}));
329
- } catch (e) {}
330
- return (_is_native_reflect_construct = function() {
331
- return !!e;
332
- })();
333
- }
334
322
  function _ts_generator(e, t) {
335
323
  var r, n, o, a = {
336
324
  label: 0,
@@ -411,6 +399,18 @@ var __rolldown_runtime__ = (function() {
411
399
  };
412
400
  }
413
401
  }
402
+ function _type_of(e) {
403
+ "@swc/helpers - typeof";
404
+ return e && typeof Symbol !== "undefined" && e.constructor === Symbol ? "symbol" : typeof e;
405
+ }
406
+ function _unsupported_iterable_to_array(e, t) {
407
+ if (!e) return;
408
+ if (typeof e === "string") return _array_like_to_array(e, t);
409
+ var r = Object.prototype.toString.call(e).slice(8, -1);
410
+ if (r === "Object" && e.constructor) r = e.constructor.name;
411
+ if (r === "Map" || r === "Set") return Array.from(r);
412
+ if (r === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)) return _array_like_to_array(e, t);
413
+ }
414
414
  var _globalThis;
415
415
  var ___rollipop_runtime__;
416
416
  var BaseDevRuntime = DevRuntime;
@@ -616,14 +616,14 @@ var __rolldown_runtime__ = (function() {
616
616
  ]);
617
617
  return e;
618
618
  }();
619
- var ReactNativeDevRuntime = /*#__PURE__*/ function(BaseDevRuntime1) {
619
+ var ReactNativeDevRuntime = /*#__PURE__*/ function(BaseDevRuntime) {
620
620
  "use strict";
621
- _inherits(ReactNativeDevRuntime1, BaseDevRuntime1);
622
- function ReactNativeDevRuntime1() {
623
- _class_call_check(this, ReactNativeDevRuntime1);
621
+ _inherits(ReactNativeDevRuntime, BaseDevRuntime);
622
+ function ReactNativeDevRuntime() {
623
+ _class_call_check(this, ReactNativeDevRuntime);
624
624
  var e;
625
625
  var t = new SocketHolder();
626
- e = _call_super(this, ReactNativeDevRuntime1, [createClientId()]), e.moduleHotContexts = /* @__PURE__ */ new Map(), e.moduleHotContextsToBeUpdated = /* @__PURE__ */ new Map(), e.moduleHotData = /* @__PURE__ */ new Map(), e.applyQueue = Promise.resolve(), e.updatesEnabled = Promise.resolve(), e.reloadRequested = false, e.lastSeq = 0;
626
+ e = _call_super(this, ReactNativeDevRuntime, [createClientId()]), e.moduleHotContexts = /* @__PURE__ */ new Map(), e.moduleHotContextsToBeUpdated = /* @__PURE__ */ new Map(), e.moduleHotData = /* @__PURE__ */ new Map(), e.applyQueue = Promise.resolve(), e.updatesEnabled = Promise.resolve(), e.reloadRequested = false, e.lastSeq = 0;
627
627
  e.socketHolder = t;
628
628
  e.hooks = {
629
629
  createModuleHotContext: function t(t) {
@@ -635,7 +635,7 @@ var __rolldown_runtime__ = (function() {
635
635
  };
636
636
  return e;
637
637
  }
638
- _create_class(ReactNativeDevRuntime1, [
638
+ _create_class(ReactNativeDevRuntime, [
639
639
  {
640
640
  key: "createHotContext",
641
641
  value: function e(e) {
@@ -756,7 +756,6 @@ var __rolldown_runtime__ = (function() {
756
756
  }
757
757
  this.lastSeq = o;
758
758
  var i = this.computeHmrUpdate(t);
759
- if (i.type === "noop") return;
760
759
  if (i.type === "full-reload") {
761
760
  this.reload(i.reason);
762
761
  return;
@@ -766,7 +765,7 @@ var __rolldown_runtime__ = (function() {
766
765
  type: "hmr:payload-delivered",
767
766
  filename: n
768
767
  }));
769
- this.applyUpdate(i);
768
+ if (i.type === "boundaries") this.applyUpdate(i);
770
769
  }
771
770
  },
772
771
  {
@@ -1025,7 +1024,7 @@ var __rolldown_runtime__ = (function() {
1025
1024
  }
1026
1025
  }
1027
1026
  ]);
1028
- return ReactNativeDevRuntime1;
1027
+ return ReactNativeDevRuntime;
1029
1028
  }(BaseDevRuntime);
1030
1029
  function createClientId() {
1031
1030
  return "".concat(Date.now().toString(36), "-").concat(Math.random().toString(36).slice(2));
@@ -1,14 +1,45 @@
1
- import { __require } from "../_virtual/_rolldown/runtime.js";
2
1
  import { asLiteral } from "../common/code.js";
2
+ import { resolvePackagePath } from "../utils/node-resolve.js";
3
+ import { createRequire } from "node:module";
3
4
  import path from "node:path";
4
5
  import { isNotNil } from "es-toolkit";
5
6
  //#region src/internal/react-native.ts
6
- function getInitializeCorePath(basePath) {
7
- return __require.resolve("react-native/Libraries/Core/InitializeCore", { paths: [basePath] });
7
+ const ASSET_REGISTRY_PATH = "react-native/asset-registry";
8
+ const LEGACY_ASSET_REGISTRY_PATH = "react-native/Libraries/Image/AssetRegistry.js";
9
+ const require = createRequire(import.meta.url);
10
+ function resolveInitializeCorePath(basePath) {
11
+ return resolveOptional("react-native/setup-env", [basePath]) ?? require.resolve("react-native/Libraries/Core/InitializeCore", { paths: [basePath] });
8
12
  }
9
- function getPolyfillScriptPaths(reactNativePath) {
10
- const scriptPath = path.join(reactNativePath, "rn-get-polyfills");
11
- return __require(scriptPath)();
13
+ function resolveAssetRegistryPath(basePath) {
14
+ return resolveOptional(ASSET_REGISTRY_PATH, [basePath]) ? ASSET_REGISTRY_PATH : LEGACY_ASSET_REGISTRY_PATH;
15
+ }
16
+ function resolvePolyfillScriptPaths(basePath, reactNativePath) {
17
+ const polyfillPath = resolveOptional("@react-native/js-polyfills", [reactNativePath, basePath]);
18
+ if (polyfillPath) return loadPolyfillScriptPaths(polyfillPath);
19
+ let metroConfigPath;
20
+ try {
21
+ metroConfigPath = resolvePackagePath(basePath, "@react-native/metro-config");
22
+ } catch {}
23
+ if (metroConfigPath) {
24
+ const requireFromMetroConfig = createRequire(path.join(metroConfigPath, "package.json"));
25
+ const metroPolyfillPath = resolveOptionalWith(() => requireFromMetroConfig.resolve("@react-native/js-polyfills"));
26
+ if (metroPolyfillPath) return loadPolyfillScriptPaths(metroPolyfillPath, requireFromMetroConfig);
27
+ }
28
+ return loadPolyfillScriptPaths(require.resolve(path.join(reactNativePath, "rn-get-polyfills")));
29
+ }
30
+ function loadPolyfillScriptPaths(modulePath, load = require) {
31
+ return load(modulePath)();
32
+ }
33
+ function resolveOptional(moduleId, paths) {
34
+ return resolveOptionalWith(() => require.resolve(moduleId, { paths }));
35
+ }
36
+ function resolveOptionalWith(resolve) {
37
+ try {
38
+ return resolve();
39
+ } catch (error) {
40
+ if (error instanceof Error && "code" in error && (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED")) return;
41
+ throw error;
42
+ }
12
43
  }
13
44
  function getGlobalVariables(dev) {
14
45
  return [
@@ -20,4 +51,4 @@ function getGlobalVariables(dev) {
20
51
  ].filter(isNotNil);
21
52
  }
22
53
  //#endregion
23
- export { getGlobalVariables, getInitializeCorePath, getPolyfillScriptPaths };
54
+ export { getGlobalVariables, resolveAssetRegistryPath, resolveInitializeCorePath, resolvePolyfillScriptPaths };
@@ -17,6 +17,10 @@ function getAgentGuide(baseUrl = defaultBaseUrl) {
17
17
  " rollipop skills get <name>",
18
18
  "",
19
19
  "Rollipop exposes build events, diagnostics, and client logs through Devframe MCP.",
20
+ "MCP requires the optional @devframes/agentic peer dependency:",
21
+ "",
22
+ " yarn add -D @devframes/agentic@1.0.0",
23
+ "",
20
24
  "Keep the dev server running while your agent uses the MCP tools.",
21
25
  "",
22
26
  "Start the dev server:",
@@ -27,8 +31,7 @@ function getAgentGuide(baseUrl = defaultBaseUrl) {
27
31
  "",
28
32
  ` MCP server: ${baseUrl}/__rollipop/__mcp`,
29
33
  "",
30
- "If you start Rollipop with custom --host, --port, or --https options,",
31
- "adjust these URLs to match the running dev server.",
34
+ "For a custom loopback --host or --port, adjust both the MCP URL and Origin.",
32
35
  "",
33
36
  "MCP setup:",
34
37
  "",
@@ -44,13 +47,12 @@ function getAgentGuide(baseUrl = defaultBaseUrl) {
44
47
  " }",
45
48
  " }",
46
49
  "",
47
- " Devframe accepts loopback origins only; keep the MCP URL and Origin on loopback.",
50
+ " Devframe requires a loopback Origin and a connection from the same machine.",
48
51
  "",
49
52
  " Tool discovery:",
50
53
  " MCP clients discover supported tools by calling tools/list.",
51
54
  " The response includes tool names, descriptions, and input schemas.",
52
- " For Streamable HTTP, initialize first, then call tools/list with",
53
- " the returned mcp-session-id header.",
55
+ " The endpoint is stateless; no mcp-session-id header is returned or required.",
54
56
  "",
55
57
  "External debugging:",
56
58
  "",
@@ -65,9 +67,11 @@ function getAgentGuide(baseUrl = defaultBaseUrl) {
65
67
  "Agent workflow:",
66
68
  "",
67
69
  " 1. Run rollipop start and keep it running while using MCP tools.",
68
- " 2. Use get_build_events before and after edits; bundler-scoped events include bundlerId.",
70
+ " 2. Start get_build_events, then make edits while it is listening.",
71
+ " Only new events are collected; bundler-scoped events include bundlerId.",
69
72
  " 3. Wait for bundle_build_done or bundle_build_failed for bundle builds.",
70
- " For HMR edits, watch for watch_change, hmr_failed, and HMR client messages.",
73
+ " For HMR edits, watch for watch_change and hmr_failed.",
74
+ " Use build_logs and build_errors when events occurred before listening.",
71
75
  " 4. Use get_bundler_status when you need the latest state for a known bundlerId.",
72
76
  " 5. Use get_console_logs, clear_console_logs, and clear_build_* for focused diagnostics.",
73
77
  " 6. Use bundleFilePath from bundle_build_done when bundle-level debugging is needed.",
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.0.0-alpha.32";
2
+ var version = "1.0.0-alpha.34";
3
3
  //#endregion
4
4
  export { version };
@@ -44,19 +44,22 @@ export declare class BundlerDevEngine {
44
44
  getBundle(): Promise<BundleStore>;
45
45
  getSourceMap(): Promise<string | undefined>;
46
46
  triggerFullBuild(): Promise<void>;
47
+ close(): Promise<void>;
47
48
  }
48
49
  export declare class BundlerPool {
49
50
  private readonly config;
50
51
  private readonly resolvedServerOptions;
51
52
  private readonly eventBus;
52
- private static readonly instances;
53
+ private readonly instances;
54
+ private closeTask;
53
55
  readonly hotUpdateStore: HotUpdateStore;
54
56
  constructor(config: ResolvedConfig, resolvedServerOptions: Required<Pick<ServerOptions, 'host' | 'port'>> & Pick<ServerOptions, 'buildOptions'>, eventBus: EventBus);
55
- get(bundleName: string, buildOptions: Pick<BuildOptions, 'platform' | 'dev'>): BundlerDevEngine;
57
+ get(bundleName: string, buildOptions: Pick<BuildOptions, 'platform' | 'dev' | 'minify'>): BundlerDevEngine;
56
58
  /**
57
59
  * 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.
58
60
  */
59
61
  getInstanceById(id: string): BundlerDevEngine | undefined;
60
62
  getInstances(): BundlerDevEngine[];
63
+ close(): Promise<void>;
61
64
  }
62
65
  //#endregion
@@ -218,12 +218,19 @@ var BundlerDevEngine = class {
218
218
  await this.ensureInitialized;
219
219
  this.devEngine.triggerFullBuild();
220
220
  }
221
+ async close() {
222
+ await this.initializeTask.catch(() => {});
223
+ const devEngine = this._devEngine;
224
+ this._devEngine = null;
225
+ await devEngine?.close();
226
+ }
221
227
  };
222
- var BundlerPool = class BundlerPool {
228
+ var BundlerPool = class {
223
229
  config;
224
230
  resolvedServerOptions;
225
231
  eventBus;
226
- static instances = /* @__PURE__ */ new Map();
232
+ instances = /* @__PURE__ */ new Map();
233
+ closeTask;
227
234
  hotUpdateStore;
228
235
  constructor(config, resolvedServerOptions, eventBus) {
229
236
  this.config = config;
@@ -233,13 +240,14 @@ var BundlerPool = class BundlerPool {
233
240
  this.hotUpdateStore.clear();
234
241
  }
235
242
  get(bundleName, buildOptions) {
243
+ invariant(this.closeTask == null, "Bundler pool is closed");
236
244
  const baseBundleName = getBaseBundleName(bundleName);
237
245
  const resolvedBuildOptions = resolveBuildOptions(this.config, {
238
246
  ...this.resolvedServerOptions.buildOptions,
239
247
  ...buildOptions
240
248
  });
241
249
  const key = `${baseBundleName}-${Bundler.createId(this.config, resolvedBuildOptions)}`;
242
- const instance = BundlerPool.instances.get(key);
250
+ const instance = this.instances.get(key);
243
251
  if (instance) return instance;
244
252
  else {
245
253
  logger.debug("Preparing new bundler instance", {
@@ -248,7 +256,7 @@ var BundlerPool = class BundlerPool {
248
256
  });
249
257
  const instance = new BundlerDevEngine(baseBundleName, { server: this.resolvedServerOptions }, this.config, resolvedBuildOptions, this.eventBus, this.hotUpdateStore);
250
258
  logger.debug("Setting new bundler instance", { key });
251
- BundlerPool.instances.set(key, instance);
259
+ this.instances.set(key, instance);
252
260
  return instance;
253
261
  }
254
262
  }
@@ -256,10 +264,19 @@ var BundlerPool = class BundlerPool {
256
264
  * 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.
257
265
  */
258
266
  getInstanceById(id) {
259
- for (const instance of BundlerPool.instances.values()) if (instance.id === id && instance.projectRoot === this.config.root) return instance;
267
+ for (const instance of this.instances.values()) if (instance.id === id && instance.projectRoot === this.config.root) return instance;
260
268
  }
261
269
  getInstances() {
262
- return Array.from(BundlerPool.instances.values());
270
+ return Array.from(this.instances.values());
271
+ }
272
+ close() {
273
+ this.closeTask ??= (async () => {
274
+ const instances = this.getInstances();
275
+ this.instances.clear();
276
+ const errors = (await Promise.allSettled(instances.map((instance) => instance.close()))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
277
+ if (errors.length > 0) throw new AggregateError(errors, "Failed to close bundler engines");
278
+ })();
279
+ return this.closeTask;
263
280
  }
264
281
  };
265
282
  //#endregion
@@ -17,6 +17,7 @@ import { DevServerState } from "./state/store.js";
17
17
  import { getWebSocketUpgradeHandler } from "./wss/server.js";
18
18
  import { HMRServer } from "./wss/hmr-server.js";
19
19
  import url from "node:url";
20
+ import { format } from "node:util";
20
21
  import { createDevServerMiddleware } from "@react-native-community/cli-server-api";
21
22
  import { createDevMiddleware } from "@react-native/dev-middleware";
22
23
  import Fastify, { LogController } from "fastify";
@@ -61,10 +62,14 @@ async function createDevServer(config, options) {
61
62
  logger: {
62
63
  info(...args) {
63
64
  if (args[0].includes("JavaScript logs have moved")) return;
64
- logger.info(...args);
65
+ logger.info(format(...args));
65
66
  },
66
- warn: logger.warn.bind(logger),
67
- error: logger.error.bind(logger)
67
+ warn(...args) {
68
+ logger.warn(format(...args));
69
+ },
70
+ error(...args) {
71
+ logger.error(format(...args));
72
+ }
68
73
  },
69
74
  unstable_experiments: {
70
75
  enableNetworkInspector: true,
@@ -142,6 +147,7 @@ async function createDevServer(config, options) {
142
147
  server.close(() => resolve());
143
148
  })));
144
149
  });
150
+ fastify.addHook("onClose", () => bundlerPool.close());
145
151
  await invokePostConfigureServer();
146
152
  eventBus.emit({
147
153
  type: "server_ready",
@@ -4,27 +4,23 @@ import { parseUrl } from "../../utils/url.js";
4
4
  import { symbolicate } from "../symbolicate.js";
5
5
  import { toDevframeEvent } from "./events.js";
6
6
  import { registerAgentTools } from "./agent/tools/index.js";
7
- import { getBuildSummary, getBundlers, getConfigInfo, getDevice, getFeatureFlags, getProjectInfo, getSnapshot } from "./data.js";
7
+ import { getConfigInfo, getDevice, getFeatureFlags, getSnapshot } from "./data.js";
8
8
  import { defineDevframe, defineRpcFunction } from "devframe";
9
9
  //#region src/server/devframe/index.ts
10
10
  const ROLLIPOP_DEVFRAME_SCOPE = "rollipop";
11
- const DASHBOARD_SHARED_STATE_KEY = "dashboard";
11
+ const DASHBOARD_EVENTS_STATE_KEY = "events";
12
12
  var RollipopDevframeController = class {
13
13
  context;
14
14
  sequence = 0;
15
- lastEvent = null;
16
- updateSharedState;
15
+ updateEventState;
17
16
  unsubscribeEventBus;
18
- refreshQueue;
19
17
  definition;
20
18
  constructor(context, agentToolContext) {
21
19
  this.context = context;
22
- const initialState = createInitialDashboardState(context);
23
- this.refreshQueue = Promise.resolve(initialState);
24
20
  this.definition = defineDevframe({
25
21
  id: "rollipop",
26
22
  name: "Rollipop",
27
- version: "1.0.0-alpha.32",
23
+ version: "1.0.0-alpha.34",
28
24
  packageName: "rollipop",
29
25
  homepage: "https://github.com/rollipop-dev/rollipop",
30
26
  description: "React Native development dashboard powered by Rollipop.",
@@ -34,13 +30,10 @@ var RollipopDevframeController = class {
34
30
  },
35
31
  setup: async (devframeContext) => {
36
32
  const scope = devframeContext.scope(ROLLIPOP_DEVFRAME_SCOPE);
37
- const sharedState = await scope.rpc.sharedState(DASHBOARD_SHARED_STATE_KEY, { initialValue: initialState });
38
- this.updateSharedState = (state) => {
33
+ const sharedState = await scope.rpc.sharedState(DASHBOARD_EVENTS_STATE_KEY, { initialValue: { lastEvent: null } });
34
+ this.updateEventState = (event) => {
39
35
  sharedState.mutate((current) => {
40
- current.snapshot = state.snapshot;
41
- current.builds = state.builds;
42
- current.featureFlags = state.featureFlags;
43
- current.lastEvent = state.lastEvent;
36
+ current.lastEvent = event;
44
37
  });
45
38
  };
46
39
  registerDashboardRpcFunctions(scope.rpc, this);
@@ -48,26 +41,19 @@ var RollipopDevframeController = class {
48
41
  this.unsubscribeEventBus = context.eventBus.subscribe((event) => {
49
42
  const data = toDevframeEvent(event);
50
43
  if (data == null) return;
51
- this.lastEvent = {
52
- sequence: ++this.sequence,
53
- data
54
- };
55
- this.refresh();
44
+ this.notify(data);
56
45
  });
57
46
  }
58
47
  });
59
48
  }
60
- async refresh() {
61
- const lastEvent = this.lastEvent;
62
- this.refreshQueue = this.refreshQueue.catch(() => createInitialDashboardState(this.context)).then(async () => {
63
- const state = await this.readDashboardState(lastEvent);
64
- this.updateSharedState?.(state);
65
- return state;
49
+ notify(data) {
50
+ this.updateEventState?.({
51
+ sequence: ++this.sequence,
52
+ data
66
53
  });
67
- return this.refreshQueue;
68
54
  }
69
- async getSnapshot() {
70
- return (await this.refresh()).snapshot;
55
+ getSnapshot() {
56
+ return getSnapshot(this.context);
71
57
  }
72
58
  getBuilds() {
73
59
  return this.context.state.getBuilds();
@@ -78,9 +64,8 @@ var RollipopDevframeController = class {
78
64
  if (this.context.bundlerPool.getInstanceById(bundlerId) == null) throw new Error(`Build logs not found: ${bundlerId}`);
79
65
  return [];
80
66
  }
81
- async deleteBuildLogs(bundlerId) {
67
+ deleteBuildLogs(bundlerId) {
82
68
  if (!this.context.state.clearBuildLogs(bundlerId) && this.context.bundlerPool.getInstanceById(bundlerId) == null) throw new Error(`Build logs not found: ${bundlerId}`);
83
- await this.refresh();
84
69
  }
85
70
  getConfig() {
86
71
  return getConfigInfo(this.context);
@@ -98,7 +83,8 @@ var RollipopDevframeController = class {
98
83
  if (pathname == null || query.platform == null || query.dev == null) throw new Error("Bundle URL must include pathname, platform, and dev query parameters");
99
84
  const bundle = await this.context.bundlerPool.get(getBaseBundleName(pathname), {
100
85
  platform: String(query.platform),
101
- dev: query.dev === "true"
86
+ dev: query.dev === "true",
87
+ ...query.minify == null ? {} : { minify: query.minify === "true" }
102
88
  }).getBundle();
103
89
  return symbolicate(bundle, [{
104
90
  file: bundleUrl,
@@ -111,33 +97,21 @@ var RollipopDevframeController = class {
111
97
  if (bundler == null) throw new Error(`Bundler not found: ${bundlerId}`);
112
98
  setTimeout(() => this.context.message.broadcast("reload"), 0);
113
99
  await bundler.triggerFullBuild();
114
- await this.refresh();
115
100
  }
116
- async reload() {
101
+ reload() {
117
102
  this.context.message.broadcast("reload");
118
- await this.refresh();
119
103
  }
120
104
  async resetCache() {
121
105
  await resetCache();
122
106
  this.context.eventBus.emit({ type: "cache_reset" });
123
- await this.refresh();
124
107
  }
125
- async resetBundlerState() {
108
+ resetBundlerState() {
126
109
  this.context.state.resetBufferedState();
127
- await this.refresh();
128
110
  }
129
111
  dispose() {
130
112
  this.unsubscribeEventBus?.();
131
113
  this.unsubscribeEventBus = void 0;
132
114
  }
133
- async readDashboardState(lastEvent) {
134
- return {
135
- snapshot: await getSnapshot(this.context),
136
- builds: this.context.state.getBuilds(),
137
- featureFlags: getFeatureFlags(this.context),
138
- lastEvent
139
- };
140
- }
141
115
  };
142
116
  function registerDashboardRpcFunctions(rpc, controller) {
143
117
  rpc.register(defineRpcFunction({
@@ -201,18 +175,5 @@ function registerDashboardRpcFunctions(rpc, controller) {
201
175
  handler: () => controller.resetBundlerState()
202
176
  }));
203
177
  }
204
- function createInitialDashboardState(context) {
205
- return {
206
- snapshot: {
207
- project: getProjectInfo(context),
208
- bundlers: getBundlers(context),
209
- devices: [],
210
- buildSummary: getBuildSummary(context)
211
- },
212
- builds: context.state.getBuilds(),
213
- featureFlags: getFeatureFlags(context),
214
- lastEvent: null
215
- };
216
- }
217
178
  //#endregion
218
- export { DASHBOARD_SHARED_STATE_KEY, ROLLIPOP_DEVFRAME_SCOPE, RollipopDevframeController };
179
+ export { DASHBOARD_EVENTS_STATE_KEY, ROLLIPOP_DEVFRAME_SCOPE, RollipopDevframeController };
@@ -16,7 +16,7 @@ async function setupDevframe(context, server, inspectorDeviceServer) {
16
16
  const controller = new RollipopDevframeController(context, createAgentToolContext(context));
17
17
  const devtools = initHub({
18
18
  name: DEVFRAME_NAME,
19
- version: "1.0.0-alpha.32",
19
+ version: "1.0.0-alpha.34",
20
20
  base: ROLLIPOP_DEVFRAME_BASE,
21
21
  cwd: context.config.root,
22
22
  ui: createUi({
@@ -44,11 +44,11 @@ async function setupDevframe(context, server, inspectorDeviceServer) {
44
44
  }
45
45
  });
46
46
  await devtools.ready;
47
- const refreshDevices = () => {
48
- controller.refresh();
47
+ const notifyDevicesChanged = () => {
48
+ controller.notify({ type: "devices_changed" });
49
49
  };
50
50
  const onDeviceConnection = (socket) => {
51
- socket.once("close", refreshDevices);
51
+ socket.once("close", notifyDevicesChanged);
52
52
  };
53
53
  inspectorDeviceServer?.on("connection", onDeviceConnection);
54
54
  server.instance.addHook("onListen", () => {
@@ -56,11 +56,10 @@ async function setupDevframe(context, server, inspectorDeviceServer) {
56
56
  const mcpUrl = getServerDisplayUrl(server.instance.listeningOrigin, "/__rollipop/__mcp");
57
57
  logger.info(`MCP server listening at ${mcpUrl}`);
58
58
  }
59
- controller.refresh();
60
59
  });
61
60
  server.instance.addHook("preClose", async () => {
62
61
  inspectorDeviceServer?.off("connection", onDeviceConnection);
63
- for (const socket of inspectorDeviceServer?.clients ?? []) socket.off("close", refreshDevices);
62
+ for (const socket of inspectorDeviceServer?.clients ?? []) socket.off("close", notifyDevicesChanged);
64
63
  controller.dispose();
65
64
  await devtools.close();
66
65
  });
@@ -55,7 +55,8 @@ function getBundleStoresByFrameUrl(stack, context) {
55
55
  const bundleName = getBaseBundleName(pathname);
56
56
  const bundler = context.bundlerPool.get(bundleName, {
57
57
  platform,
58
- dev
58
+ dev,
59
+ ...query.minify == null ? {} : { minify: query.minify === "true" }
59
60
  });
60
61
  bundleStores.set(file, bundler.getBundle());
61
62
  }
@@ -4,6 +4,16 @@ import { WebSocketServer } from "./server.js";
4
4
  import { invariant } from "es-toolkit";
5
5
  //#region src/server/wss/hmr-server.ts
6
6
  const MAX_PENDING_PAYLOADS_PER_CLIENT = 8;
7
+ const RESERVED_WEBSOCKET_EVENTS = /* @__PURE__ */ new Set([
8
+ "close",
9
+ "connection",
10
+ "error",
11
+ "headers",
12
+ "listening",
13
+ "wsClientError",
14
+ "newListener",
15
+ "removeListener"
16
+ ]);
7
17
  var HMRServer = class extends WebSocketServer {
8
18
  bundlerPool;
9
19
  eventBus;
@@ -19,9 +29,9 @@ var HMRServer = class extends WebSocketServer {
19
29
  }
20
30
  parseClientMessage(data) {
21
31
  const parsedData = JSON.parse(this.rawDataToString(data));
22
- const clientMessage = "type" in parsedData ? parsedData : null;
23
- invariant(clientMessage, "Invalid HMR client message");
24
- return clientMessage;
32
+ invariant(typeof parsedData?.type === "string", "Invalid HMR client message");
33
+ invariant(!RESERVED_WEBSOCKET_EVENTS.has(parsedData.type), `Reserved HMR event name: ${parsedData.type}`);
34
+ return parsedData;
25
35
  }
26
36
  async handleConnected(client, runtimeClientId, platform, bundleEntry) {
27
37
  const connectionToken = Symbol();
@@ -28,7 +28,7 @@ var FileStorage = class FileStorage {
28
28
  this.dataFilePath = path.join(FileStorage.getPath(basePath, { prepare: true }), DATA_FILENAME);
29
29
  if (fs.existsSync(this.dataFilePath)) {
30
30
  const loadedData = JSON.parse(fs.readFileSync(this.dataFilePath, "utf-8"));
31
- if ("version" in loadedData && loadedData.version === "1.0.0-alpha.32") this.data = loadedData;
31
+ if ("version" in loadedData && loadedData.version === "1.0.0-alpha.34") this.data = loadedData;
32
32
  else {
33
33
  this.data = createDefaultData();
34
34
  this.flush();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rollipop",
3
- "version": "1.0.0-alpha.32",
3
+ "version": "1.0.0-alpha.34",
4
4
  "homepage": "https://github.com/rollipop-dev/rollipop#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/rollipop-dev/rollipop/issues"
@@ -67,9 +67,9 @@
67
67
  "@react-native-community/cli-server-api": "20.2.0",
68
68
  "@react-native-community/cli-types": "20.2.0",
69
69
  "@react-native/dev-middleware": "0.87.1",
70
- "@rollipop/dashboard": "1.0.0-alpha.32",
71
- "@rollipop/rolldown": "1.0.32",
72
- "@swc/core": "1.15.43",
70
+ "@rollipop/dashboard": "1.0.0-alpha.34",
71
+ "@rollipop/rolldown": "1.0.34",
72
+ "@swc/core": "1.16.13",
73
73
  "@swc/helpers": "0.5.23",
74
74
  "@types/ws": "8.18.1",
75
75
  "arktype": "2.2.3",
@@ -117,7 +117,7 @@
117
117
  }
118
118
  },
119
119
  "engines": {
120
- "node": ">=22.12.0"
120
+ "node": "^22.18.0 || ^24.11.0 || >=26.0.0"
121
121
  },
122
122
  "installConfig": {
123
123
  "hoistingLimits": "none"
@@ -18,7 +18,7 @@ Use https://rollipop.dev/docs/features/env.md when changing env file loading, pr
18
18
 
19
19
  ## Experimental Features
20
20
 
21
- Use https://rollipop.dev/docs/features/experimental.md before enabling experimental options. Keep the change explicit and mention stability/version risk. For worklets specifically, read https://rollipop.dev/docs/features/reanimated-worklets.md.
21
+ Use https://rollipop.dev/docs/experimental/overview.md before enabling experimental options. Keep the change explicit and mention stability/version risk. For worklets specifically, read https://rollipop.dev/docs/experimental/reanimated-worklets.md.
22
22
 
23
23
  ## Resolve Changes
24
24
 
@@ -27,5 +27,5 @@ When changing module resolution behavior, verify package metadata (`exports`, `r
27
27
  ## Watchpoints
28
28
 
29
29
  - Translate intent from existing Metro config instead of copying Metro fields mechanically.
30
- - Overriding arrays can accidentally remove React Native defaults; check docs before replacing prelude or resolve extension lists.
30
+ - Configured `prelude`, `polyfills`, `plugins`, `resolve.sourceExtensions`, and `resolve.assetExtensions` arrays are appended to the defaults with duplicates removed. Supplying an empty array does not remove the defaults.
31
31
  - If config is produced dynamically, inspect both the source config and the resolved behavior through dev-server diagnostics when available.
@@ -10,7 +10,7 @@ Use this skill when Rollipop is installed or running and the task needs diagnost
10
10
 
11
11
  ## MCP Server
12
12
 
13
- Devframe MCP exposes structured Rollipop diagnostics whenever the dev server is running. Use it for build events, logs, cache actions, app logs, device status, reloads, build info, and symbolication. Read https://rollipop.dev/docs/features/mcp.md.
13
+ Devframe MCP exposes structured Rollipop diagnostics while the dev server is running when the optional `@devframes/agentic` peer dependency is installed. Use it for build events, logs, cache actions, app logs, device status, reloads, build info, and symbolication. Read https://rollipop.dev/docs/features/mcp.md for installation and setup.
14
14
 
15
15
  ## React Native DevTools
16
16
 
@@ -18,7 +18,7 @@ Use Rollipop's dev server for React Native HMR, source maps, multi-platform deve
18
18
 
19
19
  ## MCP Server
20
20
 
21
- The Devframe MCP endpoint is available whenever the dev server is running. Read https://rollipop.dev/docs/features/mcp.md.
21
+ The Devframe MCP endpoint is available while the dev server is running when the optional `@devframes/agentic` peer dependency is installed. Read https://rollipop.dev/docs/features/mcp.md for installation and setup.
22
22
 
23
23
  ## Interactive Tools
24
24
 
@@ -26,6 +26,6 @@ React Native DevTools and custom terminal commands belong to the dev-server work
26
26
 
27
27
  ## Watchpoints
28
28
 
29
- - Keep server options consistent with device/app URLs: host, port, HTTPS, key, and cert must match the running target.
29
+ - Keep the host and port consistent with device/app URLs. HTTPS is not supported; `--https` fails even though the CLI accepts it alongside `--key` and `--cert`.
30
30
  - Interactive mode is useful for humans; disable it for automation when needed.
31
31
  - If dev-server startup or HMR fails, switch to `troubleshooting.md`; use `debugging.md` when diagnostics tooling is needed.
@@ -522,10 +522,14 @@ describe('HMR runtime', () => {
522
522
  expect(reload).not.toHaveBeenCalled();
523
523
  });
524
524
 
525
- it('does not evaluate or acknowledge changes for modules that were not executed', async () => {
526
- const evaluate = vi.fn();
525
+ it('updates unexecuted modules without executing them until their first import', async () => {
526
+ // oxlint-disable-next-line no-eval
527
+ const evaluate = vi.fn((source: string) => (0, eval)(source));
527
528
  globalThis.globalEvalWithSourceUrl = evaluate;
528
- const { socket } = await setupRuntime();
529
+ const { runtime, socket } = await setupRuntime();
530
+ runtime.registerFactory('dep.js', 'esm', (id) => {
531
+ runtime.registerModule(id, { exports: { value: 'before' } });
532
+ });
529
533
 
530
534
  socket.emit('message', {
531
535
  data: JSON.stringify({
@@ -539,8 +543,13 @@ describe('HMR runtime', () => {
539
543
  });
540
544
 
541
545
  await new Promise((resolve) => setTimeout(resolve, 0));
542
- expect(evaluate).not.toHaveBeenCalled();
543
- expect(socket.sent).toEqual([]);
546
+ expect(runtime.isExecuted('dep.js')).toBe(false);
547
+ expect(runtime.initModule('dep.js')).toEqual({ value: 'after' });
548
+ expect(runtime.getImporters('dep.js')).toEqual(['entry.js']);
549
+ expect(evaluate).toHaveBeenCalledOnce();
550
+ expect(socket.sent.map((message) => JSON.parse(message))).toEqual([
551
+ { type: 'hmr:payload-delivered', filename: 'hmr_patch_1.js' },
552
+ ]);
544
553
  expect(reload).not.toHaveBeenCalled();
545
554
  });
546
555
 
@@ -299,9 +299,6 @@ class ReactNativeDevRuntime extends BaseDevRuntime {
299
299
  this.lastSeq = seq;
300
300
 
301
301
  const update = this.computeHmrUpdate(changedIds);
302
- if (update.type === 'noop') {
303
- return;
304
- }
305
302
  if (update.type === 'full-reload') {
306
303
  this.reload(update.reason);
307
304
  return;
@@ -311,7 +308,9 @@ class ReactNativeDevRuntime extends BaseDevRuntime {
311
308
  this.socketHolder.send(
312
309
  JSON.stringify({ type: 'hmr:payload-delivered', filename } satisfies HMRClientMessage),
313
310
  );
314
- this.applyUpdate(update);
311
+ if (update.type === 'boundaries') {
312
+ this.applyUpdate(update);
313
+ }
315
314
  }
316
315
 
317
316
  private applyInvalidate(moduleId: string, firstInvalidatedBy: string) {