bippy 0.6.1-dev.051a9b2 → 0.6.1-dev.3a24abf

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.
Files changed (55) hide show
  1. package/README.md +26 -48
  2. package/dist/core.cjs +1 -1
  3. package/dist/core.d.cts +32 -44
  4. package/dist/core.d.ts +32 -44
  5. package/dist/core.js +1 -1
  6. package/dist/core2.cjs +9 -0
  7. package/dist/core2.d.cts +3 -3
  8. package/dist/core2.d.ts +3 -3
  9. package/dist/core2.js +9 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.cts +3 -3
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.iife.js +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/install-hook-only.cjs +1 -1
  16. package/dist/install-hook-only.d.cts +1 -1
  17. package/dist/install-hook-only.d.ts +1 -1
  18. package/dist/install-hook-only.iife.js +1 -1
  19. package/dist/install-hook-only.js +1 -1
  20. package/dist/rdt-hook.cjs +1 -1
  21. package/dist/rdt-hook.js +1 -1
  22. package/dist/source.cjs +14 -4
  23. package/dist/source.d.cts +1 -1
  24. package/dist/source.d.ts +1 -1
  25. package/dist/source.js +14 -4
  26. package/dist/types.d.cts +291 -0
  27. package/dist/types.d.ts +291 -0
  28. package/package.json +14 -12
  29. package/src/core.ts +206 -155
  30. package/src/generated/react-work-tags.ts +169 -0
  31. package/src/rdt-hook.ts +24 -67
  32. package/src/react-internals.ts +67 -0
  33. package/src/source/constants.ts +1 -1
  34. package/src/source/get-source.ts +2 -1
  35. package/src/source/owner-stack.ts +64 -77
  36. package/src/source/parse-stack.ts +1 -1
  37. package/src/source/symbolication.ts +25 -4
  38. package/src/types.ts +154 -333
  39. package/src/unsubscribe.ts +2 -2
  40. package/dist/get-source.cjs +0 -19
  41. package/dist/get-source.js +0 -19
  42. package/dist/react-refresh.cjs +0 -9
  43. package/dist/react-refresh.d.cts +0 -66
  44. package/dist/react-refresh.d.ts +0 -66
  45. package/dist/react-refresh.js +0 -9
  46. package/dist/unsubscribe.d.cts +0 -298
  47. package/dist/unsubscribe.d.ts +0 -298
  48. package/src/react-refresh/constants.ts +0 -9
  49. package/src/react-refresh/detect-hmr-transport.ts +0 -33
  50. package/src/react-refresh/index.ts +0 -173
  51. package/src/react-refresh/metro-hmr-transport.ts +0 -188
  52. package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
  53. package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
  54. package/src/react-refresh/types.ts +0 -7
  55. package/src/react-refresh/vite-hmr-transport.ts +0 -116
@@ -1,11 +1,10 @@
1
+ import { getDisplayName, getType, traverseFiber } from "../core.js";
2
+ import { _renderers, getRDTHook } from "../rdt-hook.js";
1
3
  import {
2
- _renderers,
3
4
  ActivityComponentTag,
4
5
  ClassComponentTag,
5
- Fiber,
6
6
  ForwardRefTag,
7
7
  FunctionComponentTag,
8
- getRDTHook,
9
8
  HostComponentTag,
10
9
  HostHoistableTag,
11
10
  HostSingletonTag,
@@ -14,10 +13,8 @@ import {
14
13
  SuspenseComponentTag,
15
14
  SuspenseListComponentTag,
16
15
  ViewTransitionComponentTag,
17
- getDisplayName,
18
- traverseFiber,
19
- } from "../core.js";
20
- import { ServerComponentInfo } from "../types.js";
16
+ } from "../react-internals.js";
17
+ import type { Fiber, RendererDispatcherRef, ServerComponentInfo } from "../types.js";
21
18
  import {
22
19
  SERVER_FRAME_MARKER,
23
20
  SERVER_ENV_PATTERN,
@@ -28,6 +25,11 @@ import { parseDebugStack } from "./parse-debug-stack.js";
28
25
  import { parseStack, StackFrame } from "./parse-stack.js";
29
26
  import { symbolicateStack } from "./symbolication.js";
30
27
 
28
+ interface RendererDispatcherSnapshot {
29
+ currentDispatcherRef: RendererDispatcherRef;
30
+ value: unknown;
31
+ }
32
+
31
33
  export const hasDebugStack = (
32
34
  fiber: Fiber,
33
35
  ): fiber is Fiber & {
@@ -37,13 +39,10 @@ export const hasDebugStack = (
37
39
  };
38
40
 
39
41
  const isFiberOwner = (owner: Fiber | ServerComponentInfo): owner is Fiber =>
40
- typeof (owner as Fiber).tag === "number";
42
+ "tag" in owner && typeof owner.tag === "number";
41
43
 
42
- // react's typings (and bippy's Fiber, which mirrors them) declare _debugOwner
43
- // as a Fiber, but react 19 flight sets a ReactComponentInfo object for server
44
- // component owners
45
44
  const getDebugOwner = (fiber: Fiber): Fiber | ServerComponentInfo | undefined =>
46
- fiber._debugOwner as Fiber | ServerComponentInfo | undefined;
45
+ fiber._debugOwner ?? undefined;
47
46
 
48
47
  /**
49
48
  * Locates a frame inside the fiber's own function body without invoking it:
@@ -90,26 +89,41 @@ export const getDefinitionFrameFromOwnedChild = (fiber: Fiber): StackFrame | nul
90
89
  return null;
91
90
  };
92
91
 
93
- const getCurrentDispatcher = (): null | React.RefObject<unknown> => {
92
+ const getRendererDispatcherRefs = (): RendererDispatcherRef[] => {
94
93
  const rdtHook = getRDTHook();
95
- for (const renderer of [...Array.from(_renderers), ...Array.from(rdtHook.renderers.values())]) {
94
+ const renderers = new Set([..._renderers, ...rdtHook.renderers.values()]);
95
+ const currentDispatcherRefs: RendererDispatcherRef[] = [];
96
+ const seenCurrentDispatcherRefs = new Set<object>();
97
+ for (const renderer of renderers) {
96
98
  const currentDispatcherRef = renderer.currentDispatcherRef;
97
- if (currentDispatcherRef && typeof currentDispatcherRef === "object") {
98
- return "H" in currentDispatcherRef ? currentDispatcherRef.H : currentDispatcherRef.current;
99
+ if (!currentDispatcherRef || seenCurrentDispatcherRefs.has(currentDispatcherRef)) continue;
100
+ seenCurrentDispatcherRefs.add(currentDispatcherRef);
101
+ currentDispatcherRefs.push(currentDispatcherRef);
102
+ }
103
+ return currentDispatcherRefs;
104
+ };
105
+
106
+ const clearRendererDispatchers = (): RendererDispatcherSnapshot[] => {
107
+ const dispatcherSnapshots: RendererDispatcherSnapshot[] = [];
108
+ for (const currentDispatcherRef of getRendererDispatcherRefs()) {
109
+ const value =
110
+ "H" in currentDispatcherRef ? currentDispatcherRef.H : currentDispatcherRef.current;
111
+ dispatcherSnapshots.push({ currentDispatcherRef, value });
112
+ if ("H" in currentDispatcherRef) {
113
+ currentDispatcherRef.H = null;
114
+ } else {
115
+ currentDispatcherRef.current = null;
99
116
  }
100
117
  }
101
- return null;
118
+ return dispatcherSnapshots;
102
119
  };
103
120
 
104
- const setCurrentDispatcher = (value: null | React.RefObject<unknown>): void => {
105
- for (const renderer of _renderers) {
106
- const currentDispatcherRef = renderer.currentDispatcherRef;
107
- if (currentDispatcherRef && typeof currentDispatcherRef === "object") {
108
- if ("H" in currentDispatcherRef) {
109
- currentDispatcherRef.H = value;
110
- } else {
111
- currentDispatcherRef.current = value;
112
- }
121
+ const restoreRendererDispatchers = (dispatcherSnapshots: RendererDispatcherSnapshot[]): void => {
122
+ for (const { currentDispatcherRef, value } of dispatcherSnapshots) {
123
+ if ("H" in currentDispatcherRef) {
124
+ currentDispatcherRef.H = value;
125
+ } else {
126
+ currentDispatcherRef.current = value;
113
127
  }
114
128
  }
115
129
  };
@@ -151,44 +165,27 @@ const describeNativeComponentFrame = (
151
165
  (Error as { prepareStackTrace?: typeof Error.prepareStackTrace }).prepareStackTrace = undefined;
152
166
  reEntry = true;
153
167
 
154
- const previousDispatcher = getCurrentDispatcher();
155
- setCurrentDispatcher(null);
168
+ const dispatcherSnapshots = clearRendererDispatchers();
156
169
  const previousConsoleError = console.error;
157
170
  const previousConsoleWarn = console.warn;
158
171
  console.error = () => {};
159
172
  console.warn = () => {};
160
173
  try {
161
- /**
162
- * Finding a common stack frame between sample and control errors can be
163
- * tricky given the different types and levels of stack trace truncation from
164
- * different JS VMs. So instead we'll attempt to control what that common
165
- * frame should be through this object method:
166
- * Having both the sample and control errors be in the function under the
167
- * `DescribeNativeComponentFrameRoot` property, + setting the `name` and
168
- * `displayName` properties of the function ensures that a stack
169
- * frame exists that has the method name `DescribeNativeComponentFrameRoot` in
170
- * it for both control and sample stacks.
171
- */
174
+ // HACK: Run the control and sample under the same property name so their stacks share a frame.
172
175
  const RunInRootFrame = {
173
176
  DetermineComponentFrameRoot() {
174
177
  let control: unknown;
175
178
  try {
176
- // This should throw.
177
179
  if (construct) {
178
- // Something should be setting the props in the constructor.
179
180
  const ThrowingConstructor = function () {
180
181
  throw Error();
181
182
  };
182
183
  Object.defineProperty(ThrowingConstructor.prototype, "props", {
183
184
  set: function () {
184
- // We use a throwing setter instead of frozen or non-writable props
185
- // because that won't throw in a non-strict mode function.
186
185
  throw Error();
187
186
  },
188
187
  });
189
188
  if (typeof Reflect === "object" && Reflect.construct) {
190
- // We construct a different control for this case to include any extra
191
- // frames added by the construct call.
192
189
  try {
193
190
  Reflect.construct(ThrowingConstructor, []);
194
191
  } catch (caughtError) {
@@ -197,13 +194,11 @@ const describeNativeComponentFrame = (
197
194
  Reflect.construct(component, [], ThrowingConstructor);
198
195
  } else {
199
196
  try {
200
- // @ts-expect-error -- ThrowingConstructor is a constructor function
201
- ThrowingConstructor.call();
197
+ Function.prototype.apply.call(ThrowingConstructor, undefined, []);
202
198
  } catch (caughtError) {
203
199
  control = caughtError;
204
200
  }
205
- // @ts-expect-error -- ThrowingConstructor is a constructor function
206
- component.call(ThrowingConstructor.prototype);
201
+ Function.prototype.apply.call(component, ThrowingConstructor.prototype, []);
207
202
  }
208
203
  } else {
209
204
  try {
@@ -211,22 +206,17 @@ const describeNativeComponentFrame = (
211
206
  } catch (caughtError) {
212
207
  control = caughtError;
213
208
  }
214
- // TODO(luna): This will currently only throw if the function component
215
- // tries to access React/ReactDOM/props. We should probably make this throw
216
- // in simple components too
217
- const maybePromise = (component as () => Promise<unknown>)();
218
-
219
- // If the function component returns a promise, it's likely an async
220
- // component, which we don't yet support. Attach a noop catch handler to
221
- // silence the error.
222
- // TODO: Implement component stacks for async client components?
223
- // eslint-disable-next-line @typescript-eslint/no-misused-promises -- we literally check if this is a promise here
224
- if (maybePromise && typeof maybePromise.catch === "function") {
225
- maybePromise.catch(() => {});
209
+ const maybePromise = Function.prototype.apply.call(component, undefined, []);
210
+ if (
211
+ typeof maybePromise === "object" &&
212
+ maybePromise !== null &&
213
+ "catch" in maybePromise &&
214
+ typeof maybePromise.catch === "function"
215
+ ) {
216
+ maybePromise.catch(() => undefined);
226
217
  }
227
218
  }
228
219
  } catch (sample: unknown) {
229
- // This is inlined manually because closure doesn't do it for us.
230
220
  if (
231
221
  sample instanceof Error &&
232
222
  control instanceof Error &&
@@ -239,21 +229,18 @@ const describeNativeComponentFrame = (
239
229
  },
240
230
  };
241
231
 
242
- // @ts-expect-error --- displayName is not a property of the function
243
- RunInRootFrame.DetermineComponentFrameRoot.displayName = "DetermineComponentFrameRoot";
232
+ Object.defineProperty(RunInRootFrame.DetermineComponentFrameRoot, "displayName", {
233
+ value: "DetermineComponentFrameRoot",
234
+ });
244
235
  const namePropDescriptor = Object.getOwnPropertyDescriptor(
245
236
  // eslint-disable-next-line @typescript-eslint/unbound-method
246
237
  RunInRootFrame.DetermineComponentFrameRoot,
247
238
  "name",
248
239
  );
249
- // Before ES6, the `name` property was not configurable.
250
240
  if (namePropDescriptor?.configurable) {
251
- // V8 utilizes a function's `name` property when generating a stack trace.
252
241
  Object.defineProperty(
253
242
  // eslint-disable-next-line @typescript-eslint/unbound-method
254
243
  RunInRootFrame.DetermineComponentFrameRoot,
255
- // Configurable properties can be updated even if its writable descriptor
256
- // is set to `false`.
257
244
  "name",
258
245
  { value: "DetermineComponentFrameRoot" },
259
246
  );
@@ -340,7 +327,7 @@ const describeNativeComponentFrame = (
340
327
 
341
328
  Error.prepareStackTrace = previousPrepareStackTrace;
342
329
 
343
- setCurrentDispatcher(previousDispatcher);
330
+ restoreRendererDispatchers(dispatcherSnapshots);
344
331
  console.error = previousConsoleError;
345
332
  console.warn = previousConsoleWarn;
346
333
  }
@@ -353,9 +340,8 @@ const describeNativeComponentFrame = (
353
340
 
354
341
  // https://github.com/facebook/react/blob/ac3e705a18696168acfcaed39dce0cfaa6be8836/packages/react-reconciler/src/ReactFiberComponentStack.js#L180
355
342
  export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string => {
356
- const tag = fiber.tag as number;
357
343
  let stackFrame = "";
358
- switch (tag) {
344
+ switch (fiber.tag) {
359
345
  case ActivityComponentTag:
360
346
  stackFrame = describeBuiltInComponentFrame("Activity");
361
347
  break;
@@ -363,10 +349,7 @@ export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string =>
363
349
  stackFrame = describeNativeComponentFrame(fiber.type, true);
364
350
  break;
365
351
  case ForwardRefTag:
366
- stackFrame = describeNativeComponentFrame(
367
- (fiber.type as { render: React.ComponentType<unknown> }).render,
368
- false,
369
- );
352
+ stackFrame = describeNativeComponentFrame(getType(fiber.type) ?? fiber.type, false);
370
353
  break;
371
354
  case FunctionComponentTag:
372
355
  case SimpleMemoComponentTag:
@@ -375,7 +358,9 @@ export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string =>
375
358
  case HostComponentTag:
376
359
  case HostHoistableTag:
377
360
  case HostSingletonTag:
378
- stackFrame = describeBuiltInComponentFrame(fiber.type as string);
361
+ if (typeof fiber.type === "string") {
362
+ stackFrame = describeBuiltInComponentFrame(fiber.type);
363
+ }
379
364
  break;
380
365
  case LazyComponentTag:
381
366
  // TODO: When we support Thenables as component types we should rename this.
@@ -660,7 +645,9 @@ export const getParentStack = async (
660
645
  const enrichedStackFrames = fallbackStackFrames.map((stackFrame): StackFrame => {
661
646
  const isServerFrame =
662
647
  (stackFrame.source?.includes(SERVER_FRAME_MARKER) ?? false) ||
663
- (stackFrame.source != null && SERVER_ENV_PATTERN.test(stackFrame.source));
648
+ (stackFrame.source !== null &&
649
+ stackFrame.source !== undefined &&
650
+ SERVER_ENV_PATTERN.test(stackFrame.source));
664
651
 
665
652
  if (isServerFrame) {
666
653
  return getEnrichedServerStackFrame(
@@ -67,7 +67,7 @@ export const extractLocation = (
67
67
  };
68
68
 
69
69
  const applySlice = <T>(lines: T[], options?: ParseOptions): T[] => {
70
- if (options && options.slice != null) {
70
+ if (options && options.slice !== null && options.slice !== undefined) {
71
71
  if (Array.isArray(options.slice)) return lines.slice(options.slice[0], options.slice[1]);
72
72
  return lines.slice(0, options.slice);
73
73
  }
@@ -211,13 +211,28 @@ const getIgnoredSourceIndices = (rawSourceMap: StandardSourceMap): Set<number> |
211
211
  return Array.isArray(ignoreList) && ignoreList.length > 0 ? new Set(ignoreList) : undefined;
212
212
  };
213
213
 
214
+ const resolveSourceRoot = (sourceRoot: string | undefined, source: string): string => {
215
+ if (!sourceRoot || SCHEME_REGEX.test(source) || source.startsWith("/")) return source;
216
+ const normalizedSourceRoot = sourceRoot.endsWith("/") ? sourceRoot : `${sourceRoot}/`;
217
+ const normalizedSource = source.replace(/^\.\//, "");
218
+ if (SCHEME_REGEX.test(normalizedSourceRoot)) {
219
+ try {
220
+ return new URL(normalizedSource, normalizedSourceRoot).toString();
221
+ } catch {}
222
+ }
223
+ return `${normalizedSourceRoot}${normalizedSource}`;
224
+ };
225
+
226
+ const resolveSourceMapSources = (rawSourceMap: StandardSourceMap): string[] =>
227
+ rawSourceMap.sources.map((source) => resolveSourceRoot(rawSourceMap.sourceRoot, source));
228
+
214
229
  const decodeStandardSourceMap = (rawSourceMap: StandardSourceMap): SourceMap => ({
215
230
  file: rawSourceMap.file,
216
231
  ignoredSourceIndices: getIgnoredSourceIndices(rawSourceMap),
217
232
  mappings: decode(rawSourceMap.mappings),
218
233
  names: rawSourceMap.names,
219
234
  sourceRoot: rawSourceMap.sourceRoot,
220
- sources: rawSourceMap.sources,
235
+ sources: resolveSourceMapSources(rawSourceMap),
221
236
  sourcesContent: rawSourceMap.sourcesContent,
222
237
  version: 3,
223
238
  });
@@ -229,6 +244,7 @@ const decodeIndexSourceMap = (rawSourceMap: IndexSourceMap): SourceMap => {
229
244
  ...map,
230
245
  ignoredSourceIndices: getIgnoredSourceIndices(map),
231
246
  mappings: decode(map.mappings),
247
+ sources: resolveSourceMapSources(map),
232
248
  },
233
249
  offset,
234
250
  }),
@@ -282,13 +298,18 @@ const isFetchableUrl = (url: string): boolean => {
282
298
  // return null, but a dropped connection is not and must stay retryable.
283
299
  export const getSourceMapImpl = async (
284
300
  bundleUrl: string,
285
- fetchFn: (url: string) => Promise<Response> = fetch,
301
+ fetchFn?: (url: string) => Promise<Response>,
286
302
  ): Promise<null | SourceMap> => {
287
303
  if (!isFetchableUrl(bundleUrl)) {
288
304
  return null;
289
305
  }
290
306
 
291
- const bundleResponse = await fetchFn(bundleUrl);
307
+ const sourceFetch =
308
+ fetchFn ??
309
+ (typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined);
310
+ if (!sourceFetch) return null;
311
+
312
+ const bundleResponse = await sourceFetch(bundleUrl);
292
313
  if (!bundleResponse.ok) {
293
314
  return null;
294
315
  }
@@ -306,7 +327,7 @@ export const getSourceMapImpl = async (
306
327
  return null;
307
328
  }
308
329
 
309
- const sourceMapResponse = await fetchFn(sourceMapUrl);
330
+ const sourceMapResponse = await sourceFetch(sourceMapUrl);
310
331
  if (!sourceMapResponse.ok) {
311
332
  return null;
312
333
  }