@zntc/react-native 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ import type { Middleware } from './types.ts';
2
+ /**
3
+ * 미들웨어 체인(node req/res 기반)을 Bun `Request` 에 대해 실행하고 단일
4
+ * `Response` 로 변환. terminal next() / next(err) 도 node 경로(chainToHandler)와
5
+ * 동일하게 404 / 500 으로 매핑한다.
6
+ *
7
+ * route handler 가 비동기로 res.end() 를 호출하는 경우(.catch(next) 패턴)도
8
+ * Promise 가 end / next 둘 중 먼저 오는 쪽에서 resolve 되도록 한다.
9
+ */
10
+ export declare function runMiddlewareForBun(middleware: Middleware, req: Request): Promise<Response>;
@@ -1,5 +1,6 @@
1
1
  import type { IncomingMessage } from 'node:http';
2
2
  import type { Socket } from 'node:net';
3
+ import type { BunHmrClient } from '@zntc/server';
3
4
  import { type MetroHmrAdapter } from '../metro-hmr-adapter.ts';
4
5
  import type { PlatformStateCallbacks } from './platform-state.ts';
5
6
  export interface HmrBridgeOptions {
@@ -14,8 +15,22 @@ export interface HmrBridge {
14
15
  readonly adapter: MetroHmrAdapter;
15
16
  readonly callbacks: PlatformStateCallbacks;
16
17
  readonly path: string;
17
- /** http upgrade chain 안에서 호출 — channel.accept + initial greeting. */
18
+ /** http upgrade chain 안에서 호출 (Node) — channel.accept + initial greeting. */
18
19
  acceptUpgrade(req: IncomingMessage, socket: Socket): void;
20
+ /**
21
+ * Bun.serve websocket `open(ws)` 에서 호출 — Bun client 등록 + initial greeting.
22
+ * Node 의 `acceptUpgrade` 와 대칭이지만 raw socket 핸드셰이크가 없다 (Bun.serve
23
+ * 의 `server.upgrade(req)` 가 RFC6455 핸드셰이크를 native 로 처리하므로).
24
+ */
25
+ acceptBun(ws: BunHmrClient): void;
26
+ /** Bun.serve websocket `close(ws)` 에서 호출 — Bun client 정리. */
27
+ removeBun(ws: BunHmrClient): void;
28
+ /**
29
+ * Bun.serve websocket `message(ws, msg)` 에서 호출 — client → server text 를
30
+ * incoming 핸들러(register-entrypoints ACK / log forwarding)로 dispatch.
31
+ * Node 경로는 `channel.accept` 의 `socket.on('data')` 가 같은 역할.
32
+ */
33
+ handleBunMessage(ws: BunHmrClient, text: string): void;
19
34
  /** RN runtime console.log forwarding 출력 표시 toggle. 새 상태 반환. */
20
35
  toggleLogs(): boolean;
21
36
  }
@@ -30,3 +30,4 @@ export interface DevHttpServerHandle {
30
30
  */
31
31
  export declare function createBaseMiddleware(options: RnDevServerOptions, deps: DevHttpServerDeps): Middleware;
32
32
  export declare function createDevHttpServer(options: RnDevServerOptions, deps: DevHttpServerDeps): Promise<DevHttpServerHandle>;
33
+ export declare function createBunDevHttpServer(options: RnDevServerOptions, deps: DevHttpServerDeps): Promise<DevHttpServerHandle>;
@@ -2,7 +2,7 @@ export { createHmrBridge, type HmrBridge, type HmrBridgeOptions } from './hmr-br
2
2
  export { type RnDevServerHandle, serveRn, type ServeRnExtras } from './serve.ts';
3
3
  export { type CliServerApi, type CliWebsocketEndpoint, loadCliServerApi, type LoadCliServerApiOptions, } from './middleware/cli-server-api.ts';
4
4
  export { type DevMiddleware, loadDevMiddleware, type LoadDevMiddlewareOptions, } from './middleware/dev-middleware.ts';
5
- export { createBaseMiddleware, createDevHttpServer, type DevHttpServerDeps, type DevHttpServerHandle, } from './http-server.ts';
5
+ export { createBaseMiddleware, createBunDevHttpServer, createDevHttpServer, type DevHttpServerDeps, type DevHttpServerHandle, } from './http-server.ts';
6
6
  export { parseRequestUrl, readJsonBody, sendJson, sendText } from './http-utils.ts';
7
7
  export type { CustomizeFrame, RnDevServerOptions, RnDevServerOptionsInput } from './options.ts';
8
8
  export { buildRnDevServerOptions } from './options.ts';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { HMR_RN_MSG, type HmrRnErrorMessage, type HmrRnLogMessage, type HmrRnMessage, type HmrRnMessageType, type HmrRnReloadMessage, type HmrRnUpdateDoneMessage, type HmrRnUpdateMessage, type HmrRnUpdateModule, type HmrRnUpdateStartMessage, } from '@zntc/server';
2
- export { type AssetResolverOptions, type Broadcast, buildRnDevServerOptions, createBaseMiddleware, createDevHttpServer, createHmrBridge, createPlatformState, type CliServerApi, type CliWebsocketEndpoint, type DevMiddleware, loadCliServerApi, loadDevMiddleware, type LoadCliServerApiOptions, type LoadDevMiddlewareOptions, createPlatformStateRegistry, type CustomizeFrame, type DevHttpServerDeps, type DevHttpServerHandle, type FrameInfo, getCachedSourceMap, type HmrBridge, type HmrBridgeOptions, applyCustomizeFrame, createSourceMapConsumer, extractCodeFrame, handleAssetRequest, handleBundleRequest, handleHmrMapRequest, handleIndexPage, handleMapRequest, handleSymbolicateRequest, isIndexRoute, isAssetRoute, isBundleRoute, isHmrMapRoute, isMapRoute, isSymbolicateRoute, normalizeFrame, symbolicateFrame, type SymbolicateCodeFrame, type SymbolicateRequest, type SymbolicateResponse, type Middleware, type MiddlewareEnhanceContext, type PlatformState, type PlatformStateCallbacks, type PlatformStateRegistry, colors, formatLogBadge, logBundle, logError, logInfo, logWarn, postProcessSourceMap, type SourcemapPathOptions, printZntcRnBanner, resolveAssetPath, type ServeRnExtras, serveRn, setupTerminalActions, type TerminalActionsCallbacks, type TerminalActionsOptions, type RnDevServerHandle, type RnDevServerOptions, type RnDevServerOptionsInput, waitForBuild, } from './dev-server/index.ts';
2
+ export { type AssetResolverOptions, type Broadcast, buildRnDevServerOptions, createBaseMiddleware, createBunDevHttpServer, createDevHttpServer, createHmrBridge, createPlatformState, type CliServerApi, type CliWebsocketEndpoint, type DevMiddleware, loadCliServerApi, loadDevMiddleware, type LoadCliServerApiOptions, type LoadDevMiddlewareOptions, createPlatformStateRegistry, type CustomizeFrame, type DevHttpServerDeps, type DevHttpServerHandle, type FrameInfo, getCachedSourceMap, type HmrBridge, type HmrBridgeOptions, applyCustomizeFrame, createSourceMapConsumer, extractCodeFrame, handleAssetRequest, handleBundleRequest, handleHmrMapRequest, handleIndexPage, handleMapRequest, handleSymbolicateRequest, isIndexRoute, isAssetRoute, isBundleRoute, isHmrMapRoute, isMapRoute, isSymbolicateRoute, normalizeFrame, symbolicateFrame, type SymbolicateCodeFrame, type SymbolicateRequest, type SymbolicateResponse, type Middleware, type MiddlewareEnhanceContext, type PlatformState, type PlatformStateCallbacks, type PlatformStateRegistry, colors, formatLogBadge, logBundle, logError, logInfo, logWarn, postProcessSourceMap, type SourcemapPathOptions, printZntcRnBanner, resolveAssetPath, type ServeRnExtras, serveRn, setupTerminalActions, type TerminalActionsCallbacks, type TerminalActionsOptions, type RnDevServerHandle, type RnDevServerOptions, type RnDevServerOptionsInput, waitForBuild, } from './dev-server/index.ts';
3
3
  export { createMetroHmrAdapter, type MetroHmrAdapter } from './metro-hmr-adapter.ts';
4
4
  export type { CustomResolver, MetroPlatform, Resolution, ResolutionContext, } from './metro-resolver-types.ts';
5
5
  export { createAssetPlugin } from './plugins/asset.ts';
package/dist/index.js CHANGED
@@ -87,7 +87,7 @@ const URI_REGEX = /^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
87
87
  if (parsedUrl.path.indexOf("//&") === -1) {
88
88
  return urlToNormalize;
89
89
  }
90
- return (parsedUrl.schemeAndAuthority + parsedUrl.path.replace("//&", "?") + (parsedUrl.queryWithoutQuestionMark.length > 0 ? "&" + parsedUrl.queryWithoutQuestionMark : "") + parsedUrl.fragmentWithHash);
90
+ return parsedUrl.schemeAndAuthority + parsedUrl.path.replace("//&", "?") + (parsedUrl.queryWithoutQuestionMark.length > 0 ? "&" + parsedUrl.queryWithoutQuestionMark : "") + parsedUrl.fragmentWithHash;
91
91
  }
92
92
  function toJscSafeUrl(urlToConvert) {
93
93
  if (!_rfc3986Parse(urlToConvert).hasQueryPart) {
@@ -97,7 +97,7 @@ const URI_REGEX = /^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
97
97
  if (parsedUrl.queryWithoutQuestionMark.length > 0 && (parsedUrl.path === "" || parsedUrl.path === "/")) {
98
98
  throw new Error(`The given URL "${urlToConvert}" has an empty path and cannot be converted to a JSC-safe format.`);
99
99
  }
100
- return (parsedUrl.schemeAndAuthority + parsedUrl.path + (parsedUrl.queryWithoutQuestionMark.length > 0 ? "//&" + parsedUrl.queryWithoutQuestionMark.replace(/\?/g, "%3F") : "") + parsedUrl.fragmentWithHash);
100
+ return parsedUrl.schemeAndAuthority + parsedUrl.path + (parsedUrl.queryWithoutQuestionMark.length > 0 ? "//&" + parsedUrl.queryWithoutQuestionMark.replace(/\?/g, "%3F") : "") + parsedUrl.fragmentWithHash;
101
101
  }
102
102
  module.exports = { isJscSafeUrl, toNormalUrl, toJscSafeUrl };
103
103
 
@@ -110,15 +110,6 @@ var require__disabled__url = __commonJS({
110
110
  }
111
111
  });
112
112
  //#endregion
113
- //#region (optional-missing):
114
- var require__optional_missing__ = __commonJS({
115
- "(optional-missing):"(exports, module) {
116
- var e = new Error("Cannot find module \"" + "" + "\"");
117
- e.code = "MODULE_NOT_FOUND";
118
- throw e;
119
- }
120
- });
121
- //#endregion
122
113
  //#region array-set.js
123
114
  var require_source_map_lib_array_set = __commonJS({
124
115
  "array-set.js"(exports, module) {
@@ -260,7 +251,7 @@ var require_source_map_lib_mapping_list = __commonJS({
260
251
  const util = require_source_map_lib_util();
261
252
  function generatedPositionAfter(mappingA,mappingB) {
262
253
  const lineA = mappingA.generatedLine,lineB = mappingB.generatedLine,columnA = mappingA.generatedColumn,columnB = mappingB.generatedColumn;
263
- return (lineB > lineA || (lineB == lineA && columnB >= columnA) || util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0);
254
+ return lineB > lineA || lineB == lineA && columnB >= columnA || util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0;
264
255
  }
265
256
  class MappingList {
266
257
  constructor() {
@@ -525,7 +516,7 @@ const util = require_source_map_lib_util(),binarySearch = require_source_map_lib
525
516
  bias = SourceMapConsumer.GREATEST_LOWER_BOUND;
526
517
  }
527
518
  let mapping;
528
- this._wasm.withMappingCallback((m) => (mapping = m), () => {
519
+ this._wasm.withMappingCallback((m) => mapping = m, () => {
529
520
  this._wasm.exports.original_location_for(this._getMappingsPtr(), needle.generatedLine - 1, needle.generatedColumn, bias);
530
521
  });
531
522
  if (mapping) {
@@ -547,9 +538,9 @@ const util = require_source_map_lib_util(),binarySearch = require_source_map_lib
547
538
  if (!this.sourcesContent) {
548
539
  return false;
549
540
  }
550
- return (this.sourcesContent.length >= this._sources.size() && !this.sourcesContent.some(function(sc) {
541
+ return this.sourcesContent.length >= this._sources.size() && !this.sourcesContent.some(function(sc) {
551
542
  return sc == null;
552
- }));
543
+ });
553
544
  }
554
545
  sourceContentFor(aSource,nullOnMissing) {
555
546
  if (!this.sourcesContent) {
@@ -582,7 +573,7 @@ const util = require_source_map_lib_util(),binarySearch = require_source_map_lib
582
573
  bias = SourceMapConsumer.GREATEST_LOWER_BOUND;
583
574
  }
584
575
  let mapping;
585
- this._wasm.withMappingCallback((m) => (mapping = m), () => {
576
+ this._wasm.withMappingCallback((m) => mapping = m, () => {
586
577
  this._wasm.exports.generated_location_for(this._getMappingsPtr(), needle.source, needle.originalLine - 1, needle.originalColumn, bias);
587
578
  });
588
579
  if (mapping) {
@@ -616,7 +607,7 @@ const util = require_source_map_lib_util(),binarySearch = require_source_map_lib
616
607
  throw new Error("Support for url field in sections not implemented.");
617
608
  }
618
609
  const offset = util.getArg(s, "offset"),offsetLine = util.getArg(offset, "line"),offsetColumn = util.getArg(offset, "column");
619
- if (offsetLine < lastOffset.line || (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
610
+ if (offsetLine < lastOffset.line || offsetLine === lastOffset.line && offsetColumn < lastOffset.column) {
620
611
  throw new Error("Section offsets must be ordered and non-overlapping.");
621
612
  }
622
613
  lastOffset = offset;
@@ -645,7 +636,7 @@ const util = require_source_map_lib_util(),binarySearch = require_source_map_lib
645
636
  if (cmp) {
646
637
  return cmp;
647
638
  }
648
- return (aNeedle.generatedColumn - (section.generatedOffset.generatedColumn - 1));
639
+ return aNeedle.generatedColumn - (section.generatedOffset.generatedColumn - 1);
649
640
  }),section = this._sections[sectionIndex];
650
641
  if (!section) {
651
642
  return { source: null, line: null, column: null, name: null };
@@ -1511,13 +1502,13 @@ exports.SourceMapGenerator = require_source_map_lib_source_map_generator().Sourc
1511
1502
  });
1512
1503
  //#endregion
1513
1504
  //#region protocol.ts
1514
- const HMR_MSG = Object.freeze({ Connected: "connected", CssUpdate: "css-update", ClearError: "clear-error", Error: "error", FullReload: "full-reload" }),HMR_WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
1505
+ const HMR_MSG = Object.freeze({ Connected: "connected", CssUpdate: "css-update", ClearError: "clear-error", Error: "error", FullReload: "full-reload", UpdateStart: "update-start", Update: "update", UpdateDone: "update-done" }),HMR_WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
1515
1506
  function normalizeHmrErrors(errors) {
1516
1507
  if (!Array.isArray(errors) || errors.length === 0) {
1517
1508
  return [{ file: "", message: "Unknown build error" }];
1518
1509
  }
1519
1510
  return errors.map((error) => {
1520
- const e = (error ?? {}),file = typeof e.location?.file == "string" ? e.location.file : "",message = String(e.text ?? e.message ?? error);
1511
+ const e = error ?? {},file = typeof e.location?.file == "string" ? e.location.file : "",message = String(e.text ?? e.message ?? error);
1521
1512
  return { file, message };
1522
1513
  });
1523
1514
  }
@@ -1585,8 +1576,8 @@ function parseTextFrame(buffer) {
1585
1576
  //#endregion
1586
1577
  //#region hmr-channel.ts
1587
1578
  function extractErrorText(error) {
1588
- const e = (error ?? {});
1589
- return ((typeof e.stack == "string" && e.stack) || (typeof e.message == "string" && e.message) || String(error));
1579
+ const e = error ?? {};
1580
+ return typeof e.stack == "string" && e.stack || typeof e.message == "string" && e.message || String(error);
1590
1581
  }
1591
1582
  function createHmrChannel() {
1592
1583
  const nodeSockets = new Set(),bunClients = new Set(),incomingHandlers = [],connectedText = JSON.stringify({ type: HMR_MSG.Connected });
@@ -1614,14 +1605,15 @@ function createHmrChannel() {
1614
1605
  let recvBuffer = Buffer.alloc(0);
1615
1606
  socket.on("data", (chunk) => {
1616
1607
  if (incomingHandlers.length === 0)return;
1617
- recvBuffer = (recvBuffer.length === 0 ? chunk : Buffer.concat([recvBuffer, chunk]));
1608
+ recvBuffer = recvBuffer.length === 0 ? chunk : Buffer.concat([recvBuffer, chunk]);
1609
+ const reply = (text) => writeTextFrame(socket, text);
1618
1610
  while (recvBuffer.length > 0) {
1619
1611
  const parsed = parseTextFrame(recvBuffer);
1620
1612
  if (!parsed)break;
1621
1613
  recvBuffer = recvBuffer.subarray(parsed.consumed);
1622
1614
  for (const handler of incomingHandlers) {
1623
1615
  try {
1624
- handler(parsed.text, socket);
1616
+ handler(parsed.text, reply);
1625
1617
  } catch {
1626
1618
  }
1627
1619
  }
@@ -1635,6 +1627,15 @@ function createHmrChannel() {
1635
1627
  greetBun(ws);
1636
1628
  }, removeBunClient(ws) {
1637
1629
  bunClients.delete(ws);
1630
+ }, dispatchBunIncoming(ws,text) {
1631
+ if (incomingHandlers.length === 0)return;
1632
+ const reply = (out) => ws.send(out);
1633
+ for (const handler of incomingHandlers) {
1634
+ try {
1635
+ handler(text, reply);
1636
+ } catch {
1637
+ }
1638
+ }
1638
1639
  }, onIncoming(handler) {
1639
1640
  incomingHandlers.push(handler);
1640
1641
  }, broadcast(message) {
@@ -1698,7 +1699,7 @@ const BANNER_WIDTH = 59;
1698
1699
  function bannerLine(content) {
1699
1700
  const visibleLen = content.replace(/\x1b\[[0-9;]*m/g, "").length,padding = Math.max(0, BANNER_WIDTH - visibleLen),left = Math.floor(padding / 2);
1700
1701
  ;
1701
- return `${colors.cyan} ║${colors.reset}${" ".repeat(left)}${content}${" ".repeat((padding - left))}${colors.cyan}║${colors.reset}`;
1702
+ return `${colors.cyan} ║${colors.reset}${" ".repeat(left)}${content}${" ".repeat(padding - left)}${colors.cyan}║${colors.reset}`;
1702
1703
  }
1703
1704
  const ZNTC_ASCII = ["▀▀▀▀▀▀▀▀", "▀▀▀▀▀▀▀▀▀▀▀▀", "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀", "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀", "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀"],ZNTC_GRADIENT = [colors.brightYellow, colors.brightYellow, colors.yellow, colors.brightRed, colors.brightRed];
1704
1705
  if (ZNTC_ASCII.length !== ZNTC_GRADIENT.length) {
@@ -1736,7 +1737,8 @@ function buildOnRebuild(adapter,opts={}) {
1736
1737
  const annotated = annotateUpdates(event.updates, state.platform);
1737
1738
  adapter.sendUpdate(annotated);
1738
1739
  if (!opts.silent) {
1739
- logInfo(`HMR update ${colors.dim}[${state.platform}] ${event.updates.length} module(s) (${ms}ms)${colors.reset}`);
1740
+ const profileOn = process.env.ZNTC_PROFILE != null && process.env.ZNTC_PROFILE !== "",pd = profileOn ? event.phaseDurations : undefined,r = (n) => n == null ? "-" : n.toFixed(0),bd = pd ? ` ${colors.dim}[det=${r(pd.detect)} grph=${r(pd.graph)} lnk=${r(pd.link)} shk=${r(pd.shake)} emt=${r(pd.emit)} dlt=${r(pd.delta)} | gDisc=${r(pd.graphDiscover)} gBld=${r(pd.graphBuild)} eOut=${r(pd.emitOutput)} eMod=${r(pd.emitModulePass)} eCat=${r(pd.emitConcat)}]${colors.reset}` : "";
1741
+ logInfo(`HMR update ${colors.dim}[${state.platform}] ${event.updates.length} module(s) (${ms}ms)${colors.reset}${bd}`);
1740
1742
  }
1741
1743
  return;
1742
1744
  }
@@ -1746,7 +1748,7 @@ function buildOnRebuild(adapter,opts={}) {
1746
1748
  };
1747
1749
  }
1748
1750
  function buildIncomingHandler(adapter,getLogsEnabled) {
1749
- return (text, socket) => {
1751
+ return (text, reply) => {
1750
1752
  let msg = null;
1751
1753
  try {
1752
1754
  msg = JSON.parse(text);
@@ -1755,7 +1757,7 @@ function buildIncomingHandler(adapter,getLogsEnabled) {
1755
1757
  }
1756
1758
  if (!msg || typeof msg.type != "string")return;
1757
1759
  if (msg.type === "register-entrypoints") {
1758
- socket.write(buildAckFrame());
1760
+ reply(ACK_TEXT);
1759
1761
  return;
1760
1762
  }
1761
1763
  if (msg.type === "log") {
@@ -1776,10 +1778,6 @@ function buildIncomingHandler(adapter,getLogsEnabled) {
1776
1778
  };
1777
1779
  }
1778
1780
  const ACK_TEXT = JSON.stringify({ type: "bundle-registered" });
1779
- function buildAckFrame() {
1780
- const payload = Buffer.from(ACK_TEXT);
1781
- return Buffer.concat([Buffer.from([0x81, payload.length]), payload]);
1782
- }
1783
1781
  function createHmrBridge(options) {
1784
1782
  const adapter = createMetroHmrAdapter(),onRebuild = buildOnRebuild(adapter, { silent: options.silent });
1785
1783
  let logsEnabled = options.forwardClientLogs !== false;
@@ -1787,12 +1785,118 @@ function createHmrBridge(options) {
1787
1785
  return { adapter, callbacks: { onRebuild }, path: options.path, acceptUpgrade(req,socket) {
1788
1786
  adapter.channel.accept(req, socket);
1789
1787
  adapter.sendInitialGreeting();
1788
+ }, acceptBun(ws) {
1789
+ adapter.channel.addBunClient(ws);
1790
+ adapter.sendInitialGreeting();
1791
+ }, removeBun(ws) {
1792
+ adapter.channel.removeBunClient(ws);
1793
+ }, handleBunMessage(ws,text) {
1794
+ adapter.channel.dispatchBunIncoming(ws, text);
1790
1795
  }, toggleLogs() {
1791
1796
  logsEnabled = !logsEnabled;
1792
1797
  return logsEnabled;
1793
1798
  } };
1794
1799
  }
1795
1800
  //#endregion
1801
+ //#region bun-http-adapter.ts
1802
+ function createResponseShim(done) {
1803
+ const captured = { statusCode: 200, headers: {}, chunks: [] };
1804
+ let ended = false,headersSent = false;
1805
+ const applyHeaders = (headers) => {
1806
+ if (!headers)return;
1807
+ for (const [k, v] of Object.entries(headers)) {
1808
+ captured.headers[k.toLowerCase()] = Array.isArray(v) ? v.join(", ") : String(v);
1809
+ }
1810
+ },shim = { get statusCode() {
1811
+ return captured.statusCode;
1812
+ }, set statusCode(code) {
1813
+ captured.statusCode = code;
1814
+ }, get headersSent() {
1815
+ return headersSent;
1816
+ }, get writableEnded() {
1817
+ return ended;
1818
+ }, setHeader(name,value) {
1819
+ captured.headers[String(name).toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
1820
+ }, getHeader(name) {
1821
+ return captured.headers[String(name).toLowerCase()];
1822
+ }, writeHead(statusCode,headersOrReason,maybeHeaders) {
1823
+ captured.statusCode = statusCode;
1824
+ const headers = headersOrReason && typeof headersOrReason == "object" ? headersOrReason : maybeHeaders ?? undefined;
1825
+ applyHeaders(headers);
1826
+ headersSent = true;
1827
+ return shim;
1828
+ }, write(chunk) {
1829
+ if (chunk != null) {
1830
+ captured.chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
1831
+ }
1832
+ headersSent = true;
1833
+ return true;
1834
+ }, end(chunk) {
1835
+ if (ended)return shim;
1836
+ if (chunk != null) {
1837
+ captured.chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
1838
+ }
1839
+ ended = true;
1840
+ headersSent = true;
1841
+ done(captured);
1842
+ return shim;
1843
+ } };
1844
+ return { res: shim, captured };
1845
+ }
1846
+ function createRequestShim(req,rawBody) {
1847
+ const u = new URL(req.url),headers = {};
1848
+ req.headers.forEach((value, key) => {
1849
+ headers[key.toLowerCase()] = value;
1850
+ });
1851
+ const shim = { url: u.pathname + u.search, method: req.method, headers, complete: true, readable: false, rawBody, on() {
1852
+ return shim;
1853
+ } };
1854
+ return shim;
1855
+ }
1856
+ async function runMiddlewareForBun(middleware,req) {
1857
+ let rawBody = null;
1858
+ if (req.method !== "GET" && req.method !== "HEAD") {
1859
+ try {
1860
+ rawBody = await req.text();
1861
+ } catch {
1862
+ rawBody = null;
1863
+ }
1864
+ }
1865
+ const reqShim = createRequestShim(req, rawBody);
1866
+ return new Promise((resolve) => {
1867
+ let settled = false;
1868
+ const toResponse = (c) => {
1869
+ const body = c.chunks.length === 0 ? null : new Uint8Array(Buffer.concat(c.chunks));
1870
+ return new Response(body, { status: c.statusCode, headers: c.headers });
1871
+ },{ res:res } = createResponseShim((captured) => {
1872
+ if (settled)return;
1873
+ settled = true;
1874
+ resolve(toResponse(captured));
1875
+ });
1876
+ try {
1877
+ middleware(reqShim, res, (err) => {
1878
+ if (settled)return;
1879
+ settled = true;
1880
+ if (err) {
1881
+ const msg = err?.message ?? String(err);
1882
+ resolve(new Response(`Internal Server Error: ${msg}`, { status: 500 }));
1883
+ return;
1884
+ }
1885
+ if (!res.headersSent && !res.writableEnded) {
1886
+ resolve(new Response("Not Found", { status: 404 }));
1887
+ return;
1888
+ }
1889
+ resolve(new Response(null, { status: res.statusCode }));
1890
+ });
1891
+ } catch (err) {
1892
+ if (settled)return;
1893
+ settled = true;
1894
+ const msg = err?.message ?? String(err);
1895
+ resolve(new Response(`Internal Server Error: ${msg}`, { status: 500 }));
1896
+ }
1897
+ });
1898
+ }
1899
+ //#endregion
1796
1900
  //#region http-utils.ts
1797
1901
  function sendText(res,statusCode,text,contentType="text/plain") {
1798
1902
  res.writeHead(statusCode, { "Content-Type": contentType, "Content-Length": Buffer.byteLength(text) });
@@ -1979,7 +2083,7 @@ function resolveAssetPath(urlPathname,opts) {
1979
2083
  }
1980
2084
  if (normalizedPath.startsWith("node_modules")) {
1981
2085
  try {
1982
- const modulePath = normalizedPath.replace(/^node_modules[/\\]/, ""),packageName = modulePath.startsWith("@") ? (modulePath.match(/^(@[^/\\]+[/\\][^/\\]+)/)?.[1] ?? modulePath.split(sep)[0]) : modulePath.split(sep)[0];
2086
+ const modulePath = normalizedPath.replace(/^node_modules[/\\]/, ""),packageName = modulePath.startsWith("@") ? modulePath.match(/^(@[^/\\]+[/\\][^/\\]+)/)?.[1] ?? modulePath.split(sep)[0] : modulePath.split(sep)[0];
1983
2087
  if (!packageName)return null;
1984
2088
  const packageRelativePath = modulePath.slice(packageName.length + 1);
1985
2089
  let packageJsonPath;
@@ -2024,7 +2128,7 @@ async function handleAssetRequest(_req,res,url,opts) {
2024
2128
  const ext = extname(resolved).toLowerCase();
2025
2129
  ;
2026
2130
  const content = await readFile(resolved);
2027
- res.writeHead(200, { "Content-Type": (CONTENT_TYPE_MAP[ext] ?? "application/octet-stream"), "Cache-Control": "public, max-age=31536000", "Content-Length": content.length });
2131
+ res.writeHead(200, { "Content-Type": CONTENT_TYPE_MAP[ext] ?? "application/octet-stream", "Cache-Control": "public, max-age=31536000", "Content-Length": content.length });
2028
2132
  res.end(content);
2029
2133
  } catch {
2030
2134
  sendText(res, 500, "Internal Server Error");
@@ -2276,7 +2380,7 @@ function deepMerge(base,override) {
2276
2380
  return result;
2277
2381
  }
2278
2382
  function buildRnBundleOptions(input) {
2279
- const { entry:entry, projectRoot:projectRoot, rnPlatform:rnPlatform, dev:dev, sourcemap:sourcemap, minify:minify, dropConsole:dropConsole, dropDebugger:dropDebugger, extra:extra } = input;
2383
+ const { entry:entry, projectRoot:projectRoot, rnPlatform:rnPlatform, dev:dev, sourcemap:sourcemap, minify:minify, watchDelay:watchDelay, dropConsole:dropConsole, dropDebugger:dropDebugger, extra:extra } = input;
2280
2384
  if (extra?.platforms && !extra.platforms.includes(rnPlatform)) {
2281
2385
  throw new Error(`extra.platforms (${JSON.stringify(extra.platforms)}) does not include the active rnPlatform '${rnPlatform}'`);
2282
2386
  }
@@ -2299,7 +2403,7 @@ function buildRnBundleOptions(input) {
2299
2403
  if (extra?.prelude && extra.prelude.length > 0) {
2300
2404
  for (const p of extra.prelude)runBeforeMain.push(resolve$4(projectRoot, p));
2301
2405
  }
2302
- const define = { global: "__ZNTC_RN_GLOBAL__", __DEV__: String(dev), "process.env.NODE_ENV": `"${dev ? "development" : "production"}"`, "process.env.EXPO_ROUTER_APP_ROOT": JSON.stringify(resolve$4(projectRoot, "app")), "process.env.EXPO_ROUTER_IMPORT_MODE": "\"sync\"", "process.env.EXPO_OS": `"${rnPlatform}"` },baseLoader = buildAssetLoaders(assetExts),preset = { entryPoints: [resolve$4(projectRoot, entry)], platform: "react-native", sourcemap: sourcemap ?? dev, minify: minify ?? false, dropConsole: dropConsole ?? false, dropDebugger: dropDebugger ?? false, plugins, emitDiskSourcemap: !dev, target: "es5", flow: true, jsxInJs: true, configurableExports: true, strictExecutionOrder: true, inlineDynamicImports: true, workletTransform: true, codegenTransform: true, resolveExtensions: buildResolveExtensions(rnPlatform, sourceExts), mainFields: ["react-native", "browser", "main"], loader: baseLoader, alias: buildRnSingletonAliases(projectRoot), preserveSymlinks: true, resolveSymlinkSiblings: true, define, banner: buildPrelude(input), globalIdentifiers: [...RN_GLOBAL_IDENTIFIERS] };
2406
+ const define = { global: "__ZNTC_RN_GLOBAL__", __DEV__: String(dev), "process.env.NODE_ENV": `"${dev ? "development" : "production"}"`, "process.env.EXPO_ROUTER_APP_ROOT": JSON.stringify(resolve$4(projectRoot, "app")), "process.env.EXPO_ROUTER_IMPORT_MODE": "\"sync\"", "process.env.EXPO_OS": `"${rnPlatform}"` },baseLoader = buildAssetLoaders(assetExts),preset = { entryPoints: [resolve$4(projectRoot, entry)], platform: "react-native", sourcemap: sourcemap ?? dev, minify: minify ?? false, ...watchDelay !== undefined ? { watchDelay } : {}, dropConsole: dropConsole ?? false, dropDebugger: dropDebugger ?? false, plugins, emitDiskSourcemap: !dev, target: "es5", flow: true, jsxInJs: true, configurableExports: true, strictExecutionOrder: true, inlineDynamicImports: true, workletTransform: true, codegenTransform: true, preserveSafePlugins: extra?.preserveSafePlugins ?? !(extra?.additionalPlugins && extra.additionalPlugins.length > 0 || extra?.metroResolveRequest || extra?.babelTransformerPath), resolveExtensions: buildResolveExtensions(rnPlatform, sourceExts), mainFields: ["react-native", "browser", "main"], loader: baseLoader, alias: buildRnSingletonAliases(projectRoot), preserveSymlinks: true, resolveSymlinkSiblings: true, define, banner: buildPrelude(input), globalIdentifiers: [...RN_GLOBAL_IDENTIFIERS] };
2303
2407
  if (polyfills.length > 0)preset.polyfills = polyfills;
2304
2408
  if (runBeforeMain.length > 0)preset.runBeforeMain = runBeforeMain;
2305
2409
  const workletVersion = resolveWorkletPluginVersion(projectRoot, input.workletPluginVersion);
@@ -2366,7 +2470,7 @@ function isVirtualSource(stripped) {
2366
2470
  function isFrameworkSource(src) {
2367
2471
  if (typeof src != "string")return false;
2368
2472
  const stripped = stripVirtualPrefix(src);
2369
- return (stripped === "__prelude__" || stripped.includes("?ctx=") || /(?:^|[/\\])node_modules[/\\]/.test(stripped) || stripped.startsWith("zntc:"));
2473
+ return stripped === "__prelude__" || stripped.includes("?ctx=") || /(?:^|[/\\])node_modules[/\\]/.test(stripped) || stripped.startsWith("zntc:");
2370
2474
  }
2371
2475
  function postProcessSourceMap(rawJson,pathOpts) {
2372
2476
  try {
@@ -2376,7 +2480,7 @@ function postProcessSourceMap(rawJson,pathOpts) {
2376
2480
  if (pathOpts?.sourceRoot === undefined && map.sourceRoot === "") {
2377
2481
  delete map.sourceRoot;
2378
2482
  }
2379
- const existing = new Set([...(Array.isArray(map.x_google_ignoreList) ? map.x_google_ignoreList : []), ...(Array.isArray(map.ignoreList) ? map.ignoreList : [])].filter((v) => Number.isInteger(v) && v >= 0));
2483
+ const existing = new Set([...Array.isArray(map.x_google_ignoreList) ? map.x_google_ignoreList : [], ...Array.isArray(map.ignoreList) ? map.ignoreList : []].filter((v) => Number.isInteger(v) && v >= 0));
2380
2484
  for (let i = 0; i < map.sources.length; i++) {
2381
2485
  if (isFrameworkSource(map.sources[i]))existing.add(i);
2382
2486
  }
@@ -2527,7 +2631,7 @@ function createPlatformStateRegistry(options,callbacks) {
2527
2631
  function resolvePlatform(url,registry,defaultPlatform) {
2528
2632
  const param = url.searchParams.get("platform");
2529
2633
  ;
2530
- return registry.getOrCreate((param === "ios" || param === "android" ? param : defaultPlatform));
2634
+ return registry.getOrCreate(param === "ios" || param === "android" ? param : defaultPlatform);
2531
2635
  }
2532
2636
  //#endregion
2533
2637
  //#region bundle.ts
@@ -2706,7 +2810,7 @@ async function createSourceMapConsumer(sourceMapJson) {
2706
2810
  return null;
2707
2811
  }
2708
2812
  try {
2709
- const mod = (await Promise.resolve().then(()=>require_source_map_source_map()));
2813
+ const mod = await Promise.resolve().then(()=>require_source_map_source_map());
2710
2814
  return await new mod.SourceMapConsumer(parsed);
2711
2815
  } catch {
2712
2816
  return null;
@@ -2914,7 +3018,53 @@ async function createDevHttpServer(options,deps) {
2914
3018
  return { server, url: `http://${options.host}:${options.port}`, port: options.port, stop: () => new Promise((resolve, reject) => {
2915
3019
  server.removeListener("request", requestHandler);
2916
3020
  if (upgradeHandler)server.removeListener("upgrade", upgradeHandler);
2917
- server.close((err) => (err ? reject(err) : resolve()));
3021
+ server.close((err) => err ? reject(err) : resolve());
3022
+ }) };
3023
+ }
3024
+ function createBunHttpServerShim() {
3025
+ let warned = false;
3026
+ const shim = { on(event) {
3027
+ if (event === "upgrade" && !warned) {
3028
+ warned = true;
3029
+ process.stderr.write(`[zntc:rn-dev] Bun runtime: enhanceMiddleware 의 httpServer.on('upgrade') 는 ` + `Bun.serve 에서 지원되지 않습니다(raw socket 미노출). HMR(/hot)은 정상 동작하나 ` + `WebSocket upgrade 에 의존하는 enhanceMiddleware 기능(Rozenite 등)은 제한됩니다.\n`);
3030
+ }
3031
+ return shim;
3032
+ }, once() {
3033
+ return shim;
3034
+ }, removeListener() {
3035
+ return shim;
3036
+ }, off() {
3037
+ return shim;
3038
+ }, address() {
3039
+ return { port: 0 };
3040
+ } };
3041
+ return shim;
3042
+ }
3043
+ async function createBunDevHttpServer(options,deps) {
3044
+ const Bun = globalThis.Bun;
3045
+ if (!Bun)throw new Error("createBunDevHttpServer는 Bun runtime에서만 호출 가능합니다.");
3046
+ const baseMiddleware = createBaseMiddleware(options, deps),enhanced = options.enhanceMiddleware ? options.enhanceMiddleware(baseMiddleware, { httpServer: createBunHttpServerShim() }) : baseMiddleware,hmr = deps.hmrBridge,serveOpts = { port: options.port, hostname: options.host, async fetch(req,server) {
3047
+ const url = new URL(req.url);
3048
+ if (hmr && (url.pathname === hmr.path || url.pathname.startsWith(`${hmr.path}?`))) {
3049
+ if (server.upgrade(req))return undefined;
3050
+ return new Response("Upgrade required", { status: 426 });
3051
+ }
3052
+ return runMiddlewareForBun(enhanced, req);
3053
+ } };
3054
+ if (hmr) {
3055
+ serveOpts.websocket = { open(ws) {
3056
+ hmr.acceptBun(ws);
3057
+ }, message(ws,message) {
3058
+ const text = typeof message == "string" ? message : message.toString("utf-8");
3059
+ hmr.handleBunMessage(ws, text);
3060
+ }, close(ws) {
3061
+ hmr.removeBun(ws);
3062
+ } };
3063
+ }
3064
+ const server = Bun.serve(serveOpts);
3065
+ return { server: server, url: `http://${options.host}:${server.port}`, port: server.port, stop: () => new Promise((resolve) => {
3066
+ server.stop(true);
3067
+ resolve();
2918
3068
  }) };
2919
3069
  }
2920
3070
  //#endregion
@@ -2937,7 +3087,7 @@ function resolveCliServerApiPath(projectRoot) {
2937
3087
  }
2938
3088
  async function loadCliServerApi(options) {
2939
3089
  try {
2940
- const resolvedPath = resolveCliServerApiPath(options.projectRoot),mod = (await import(resolvedPath)),result = mod.createDevServerMiddleware({ port: options.port, host: options.host, watchFolders: [] });
3090
+ const resolvedPath = resolveCliServerApiPath(options.projectRoot),mod = await import(resolvedPath),result = mod.createDevServerMiddleware({ port: options.port, host: options.host, watchFolders: [] });
2941
3091
  return { websocketEndpoints: result.websocketEndpoints, broadcast: result.messageSocketEndpoint.broadcast };
2942
3092
  } catch (err) {
2943
3093
  if (process.env.ZNTC_DEBUG_TERMINAL === "1") {
@@ -3130,7 +3280,9 @@ async function serveRn(options,extras={}) {
3130
3280
  process.stderr.write(`[zntc:rn-dev:debug] cli-server-api: ${cliServerApi ? "loaded" : "null (peer 미설치 또는 load 실패)"}\n`);
3131
3281
  process.stderr.write(`[zntc:rn-dev:debug] dev-middleware: ${devMiddleware ? "loaded" : "null (peer 미설치)"}\n`);
3132
3282
  }
3133
- const hmrBridge = options.hmr ? createHmrBridge({ path: HMR_PATH, silent: extras.silent, forwardClientLogs: options.bundle.extra?.forwardClientLogs }) : undefined,platforms = createPlatformStateRegistry(options, hmrBridge?.callbacks),httpHandle = await createDevHttpServer(options, { broadcast, platforms, hmrBridge, devMiddleware: devMiddleware ?? undefined, cliServerApi: cliServerApi ?? undefined }),buildStart = Date.now(),firstState = platforms.getOrCreate(options.bundle.rnPlatform);
3283
+ const hmrBridge = options.hmr ? createHmrBridge({ path: HMR_PATH, silent: extras.silent, forwardClientLogs: options.bundle.extra?.forwardClientLogs }) : undefined,platforms = createPlatformStateRegistry(options, hmrBridge?.callbacks),isBun = typeof globalThis.Bun < "u";
3284
+ ;
3285
+ const httpHandle = await (isBun ? createBunDevHttpServer : createDevHttpServer)(options, { broadcast, platforms, hmrBridge, devMiddleware: devMiddleware ?? undefined, cliServerApi: cliServerApi ?? undefined }),buildStart = Date.now(),firstState = platforms.getOrCreate(options.bundle.rnPlatform);
3134
3286
  await waitForBuild(firstState);
3135
3287
  if (!extras.silent) {
3136
3288
  if (firstState.buildError) {
@@ -3238,7 +3390,7 @@ function parserPluginsFor(filename) {
3238
3390
  }
3239
3391
  function isRootImportPluginName(name) {
3240
3392
  const normalized = name.replace(/\\/g, "/");
3241
- return (normalized === "root-import" || normalized === "babel-plugin-root-import" || normalized.endsWith("/babel-plugin-root-import") || normalized.includes("/babel-plugin-root-import/"));
3393
+ return normalized === "root-import" || normalized === "babel-plugin-root-import" || normalized.endsWith("/babel-plugin-root-import") || normalized.includes("/babel-plugin-root-import/");
3242
3394
  }
3243
3395
  function injectRootImportRootOption(options,projectRoot) {
3244
3396
  const next = options ? { ...options } : {},paths = next.paths;
@@ -3463,9 +3615,9 @@ function withExpo(config) {
3463
3615
  if (winter)expoModules.push(winter);
3464
3616
  if (metroRuntime)expoModules.push(metroRuntime);
3465
3617
  const existingAssetExts = config.resolver?.assetExts ?? [],existingNormalized = new Set(existingAssetExts.map(normalizeExt));
3466
- return { ...config, resolver: { ...config.resolver, assetExts: [...existingAssetExts, ...EXPO_ASSET_EXTS.filter((ext) => !existingNormalized.has(ext))], blockList: [...(config.resolver?.blockList ?? []), ...EXPO_BLOCK_LIST] }, serializer: { ...config.serializer, prelude: [...(config.serializer?.prelude ?? []), ...expoModules] }, server: { ...config.server, silentConsoleErrorPatterns: [...(config.server?.silentConsoleErrorPatterns ?? []), WINTER_POLYFILL_WARNING_PATTERN] } };
3618
+ return { ...config, resolver: { ...config.resolver, assetExts: [...existingAssetExts, ...EXPO_ASSET_EXTS.filter((ext) => !existingNormalized.has(ext))], blockList: [...config.resolver?.blockList ?? [], ...EXPO_BLOCK_LIST] }, serializer: { ...config.serializer, prelude: [...config.serializer?.prelude ?? [], ...expoModules] }, server: { ...config.server, silentConsoleErrorPatterns: [...config.server?.silentConsoleErrorPatterns ?? [], WINTER_POLYFILL_WARNING_PATTERN] } };
3467
3619
  }
3468
3620
  //#endregion
3469
3621
  //#region index.ts
3470
- export { HMR_RN_MSG, buildRnDevServerOptions, createBaseMiddleware, createDevHttpServer, createHmrBridge, createPlatformState, loadCliServerApi, loadDevMiddleware, createPlatformStateRegistry, getCachedSourceMap, applyCustomizeFrame, createSourceMapConsumer, extractCodeFrame, handleAssetRequest, handleBundleRequest, handleHmrMapRequest, handleIndexPage, handleMapRequest, handleSymbolicateRequest, isIndexRoute, isAssetRoute, isBundleRoute, isHmrMapRoute, isMapRoute, isSymbolicateRoute, normalizeFrame, symbolicateFrame, colors, formatLogBadge, logBundle, logError, logInfo, logWarn, postProcessSourceMap, printZntcRnBanner, resolveAssetPath, serveRn, setupTerminalActions, waitForBuild, createMetroHmrAdapter, createAssetPlugin, createBabelPlugin, createBabelTransformer, detectCustomPlugins, isZntcNativePlugin, ZNTC_NATIVE_PLUGIN_PATTERNS, CODEGEN_NATIVE_COMPONENT_MARKER, createCodegenPlugin, createCodegenTransformer, escapeRegex, createMetroResolveRequestPlugin, createRequireContextPlugin, createStyledComponentsNativePlugin, disableStyledComponentsNativeDomProbe, STYLED_COMPONENTS_NATIVE_PATH_RE, buildRnBundleOptions, bundleRn, DEFAULT_ASSET_EXTS, watchRn, resolveRnPolyfills, RN_GLOBAL_IDENTIFIERS, tryResolve, HMR_CLIENT_SUFFIX, ZNTC_HMR_CLIENT_CODE, detectExpo, WINTER_POLYFILL_WARNING_PATTERN, withExpo };
3622
+ export { HMR_RN_MSG, buildRnDevServerOptions, createBaseMiddleware, createBunDevHttpServer, createDevHttpServer, createHmrBridge, createPlatformState, loadCliServerApi, loadDevMiddleware, createPlatformStateRegistry, getCachedSourceMap, applyCustomizeFrame, createSourceMapConsumer, extractCodeFrame, handleAssetRequest, handleBundleRequest, handleHmrMapRequest, handleIndexPage, handleMapRequest, handleSymbolicateRequest, isIndexRoute, isAssetRoute, isBundleRoute, isHmrMapRoute, isMapRoute, isSymbolicateRoute, normalizeFrame, symbolicateFrame, colors, formatLogBadge, logBundle, logError, logInfo, logWarn, postProcessSourceMap, printZntcRnBanner, resolveAssetPath, serveRn, setupTerminalActions, waitForBuild, createMetroHmrAdapter, createAssetPlugin, createBabelPlugin, createBabelTransformer, detectCustomPlugins, isZntcNativePlugin, ZNTC_NATIVE_PLUGIN_PATTERNS, CODEGEN_NATIVE_COMPONENT_MARKER, createCodegenPlugin, createCodegenTransformer, escapeRegex, createMetroResolveRequestPlugin, createRequireContextPlugin, createStyledComponentsNativePlugin, disableStyledComponentsNativeDomProbe, STYLED_COMPONENTS_NATIVE_PATH_RE, buildRnBundleOptions, bundleRn, DEFAULT_ASSET_EXTS, watchRn, resolveRnPolyfills, RN_GLOBAL_IDENTIFIERS, tryResolve, HMR_CLIENT_SUFFIX, ZNTC_HMR_CLIENT_CODE, detectExpo, WINTER_POLYFILL_WARNING_PATTERN, withExpo };
3471
3623
  //#endregion
package/dist/preset.d.ts CHANGED
@@ -12,6 +12,8 @@ export interface RnBundleInput {
12
12
  dev: boolean;
13
13
  /** sourcemap emit. dev 시 inline 권장. */
14
14
  sourcemap?: boolean;
15
+ /** watch 디바운스(ms) — dev server watch 의 첫 이벤트 후 idle 병합 윈도우. 기본 16(native). */
16
+ watchDelay?: number;
15
17
  /** prod build 의 minify. */
16
18
  minify?: boolean;
17
19
  /** console.* 호출 제거. Metro production Babel plugin 과 같은 정책을 원하는 release 경로에서 사용. */
@@ -49,6 +51,15 @@ export interface RnBundleInput {
49
51
  metroResolveRequest?: CustomResolver;
50
52
  /** Metro 호환 babel transformer path (svg-transformer 등). */
51
53
  babelTransformerPath?: string;
54
+ /**
55
+ * HMR 위상 보존 plugin 게이트 명시 override. 미지정(undefined)이면 preset 이 보수적으로
56
+ * 판정한다 — 내장 plugin 만이면 true, additionalPlugins/metroResolveRequest/
57
+ * babelTransformerPath 중 하나라도 있으면 false. 사용자가 "내 resolver/transformer 가
58
+ * 결정적·모듈별 순수(같은 입력→같은 출력, 전역 상태 무의존)"임을 보장할 수 있으면 true 로
59
+ * 명시해 보존을 강제(=HMR rebuild 가속)할 수 있다. 비결정이면 stale 번들 위험은 사용자 책임.
60
+ * false 명시로 강제 비활성도 가능.
61
+ */
62
+ preserveSafePlugins?: boolean;
52
63
  /** RN sourceExts override (default RN preset). */
53
64
  sourceExts?: string[];
54
65
  /** RN assetExts override (default RN preset). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zntc/react-native",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "ZNTC React Native platform — Metro HMR adapter + RN preset (buildRnBundleOptions) + plugin factories + RN runtime",
5
5
  "keywords": [
6
6
  "bundler",
@@ -48,13 +48,13 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@react-native-community/cli-server-api": "^15.0.0",
51
- "@zntc/core": "0.1.1",
51
+ "@zntc/core": "0.1.2",
52
52
  "jsc-safe-url": "^0.2.4",
53
53
  "source-map": "^0.7.4"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/node": "^25.5.2",
57
- "@zntc/server": "0.1.1"
57
+ "@zntc/server": "0.1.2"
58
58
  },
59
59
  "optionalDependencies": {
60
60
  "@babel/core": "^7.26.0",
@@ -5,8 +5,9 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- var REFRESH_TEXT_COLOR = -1; // #ffffff
9
- var REFRESH_BACKGROUND_COLOR = -14318360; // #2584e8
8
+ // JSI 색상을 uint32 로 기대 — 음수(-1)는 HostFunction throw 위험. unsigned hex 사용.
9
+ var REFRESH_TEXT_COLOR = 0xffffffff; // #ffffff
10
+ var REFRESH_BACKGROUND_COLOR = 0xff2584e8; // #2584e8
10
11
  var BUFFER_LIMIT = 1024 * 1024;
11
12
  var prettyFormat = require('pretty-format');
12
13
  var prettyFormatImpl = prettyFormat && (prettyFormat.default || prettyFormat);
@@ -68,13 +69,10 @@ function getDirectNativeDevLoadingView() {
68
69
 
69
70
  function wrapNativeDevLoadingView(nativeDevLoadingView) {
70
71
  return {
71
- showMessage: function (message, _type, options) {
72
- nativeDevLoadingView.showMessage(
73
- message,
74
- REFRESH_TEXT_COLOR,
75
- REFRESH_BACKGROUND_COLOR,
76
- !!(options && options.dismissButton),
77
- );
72
+ showMessage: function (message, _type, _options) {
73
+ // native DevLoadingView.showMessage 시그니처는 (message, color, backgroundColor) 3개.
74
+ // 4번째 인자를 넘기면 JSI HostFunction 이 arg-count mismatch 로 throw → 배너 미표시.
75
+ nativeDevLoadingView.showMessage(message, REFRESH_TEXT_COLOR, REFRESH_BACKGROUND_COLOR);
78
76
  },
79
77
  hide: function () {
80
78
  nativeDevLoadingView.hide();
@@ -136,11 +134,25 @@ var HMRClient = {
136
134
  },
137
135
 
138
136
  _showRefreshing: function () {
137
+ this._showTime = Date.now();
139
138
  this._safeCallDlv('showMessage', ['Refreshing...', 'refresh']);
140
139
  },
141
140
 
141
+ // ZNTC 의 HMR apply 는 동기라 update-start→update-done 이 거의 즉시 일어난다.
142
+ // 그대로 hide 하면 'Refreshing...' 배너가 한 프레임도 못 그려지고 사라진다.
143
+ // 최소 표시 시간(MIN_SHOW_MS)을 보장해 Metro 와 체감을 맞춘다. 그 사이 새 update 가
144
+ // 시작되면(_pendingUpdates>0) hide 를 건너뛰어 배너를 유지한다.
142
145
  _hideRefreshing: function () {
143
- this._safeCallDlv('hide', []);
146
+ var self = this;
147
+ var MIN_SHOW_MS = 300;
148
+ var elapsed = Date.now() - (self._showTime || 0);
149
+ if (elapsed >= MIN_SHOW_MS) {
150
+ self._safeCallDlv('hide', []);
151
+ return;
152
+ }
153
+ setTimeout(function () {
154
+ if (self._pendingUpdates === 0) self._safeCallDlv('hide', []);
155
+ }, MIN_SHOW_MS - elapsed);
144
156
  },
145
157
 
146
158
  enable: function () {