@tanstack/redact 0.0.9 → 0.0.11

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.
@@ -209,20 +209,15 @@ function rerenderFiber(fiber: Fiber, root: FiberRoot): void {
209
209
  // Element → children normalization
210
210
  // ---------------------------------------------------------------------------
211
211
 
212
- type TextChild = { _text: string }
213
- type NormalizedChild = ReactElement | TextChild | null
214
-
215
- // NEVER use `'_text' in child` to distinguish text wrappers from elements.
216
- // TanStack's RSC renderable proxies (createRscProxy with renderable: true) are
217
- // Proxy wrappers around real React elements whose `has` trap returns `true`
218
- // for ANY string key — so `'_text' in rscProxy` is TRUE even though the proxy
219
- // is an element. That misidentification set a Text fiber's `pendingProps` to
220
- // `child._text` (another chained RSC proxy), which then rendered as
221
- // `[object Object]` when createTextNode stringified the element. React
222
- // elements always carry `$$typeof`; our text wrapper never does — so the
223
- // presence of `$$typeof` is the invariant we rely on.
224
- function isTextChild(child: Exclude<NormalizedChild, null>): child is TextChild {
225
- return (child as any).$$typeof === undefined
212
+ // Text children pass through as raw strings — no wrapper. The previous
213
+ // `{_text: string}` shape allocated tens of thousands of objects per
214
+ // stable-list re-render and dominated minor-GC pressure. `typeof === 'string'`
215
+ // is also robust to RSC renderable proxies (which have `has` traps that
216
+ // would fool a `'_text' in child` predicate but can't fool `typeof`).
217
+ type NormalizedChild = ReactElement | string | null
218
+
219
+ function isTextChild(child: Exclude<NormalizedChild, null>): child is string {
220
+ return typeof child === 'string'
226
221
  }
227
222
 
228
223
  export function childrenToArray(children: ReactNode): NormalizedChild[] {
@@ -233,11 +228,15 @@ export function childrenToArray(children: ReactNode): NormalizedChild[] {
233
228
 
234
229
  function pushChildren(node: ReactNode, out: NormalizedChild[]): void {
235
230
  if (node == null || typeof node === 'boolean') return
236
- if (typeof node === 'string' || typeof node === 'number') {
231
+ if (typeof node === 'string') {
237
232
  // Empty strings render no text node (matches React + the `<!-- -->`
238
233
  // separator elision on the SSR side so server/client agree).
239
234
  if (node === '') return
240
- out.push({ _text: '' + node })
235
+ out.push(node)
236
+ return
237
+ }
238
+ if (typeof node === 'number') {
239
+ out.push('' + node)
241
240
  return
242
241
  }
243
242
  if (Array.isArray(node)) {
@@ -298,7 +297,7 @@ function fiberFromChild(child: NormalizedChild, parent: Fiber): Fiber {
298
297
  if (!child) return createFiber(FiberTag.Fragment, null, null)
299
298
  if (isTextChild(child)) {
300
299
  const f = createFiber(FiberTag.Text, null, null)
301
- f.pendingProps = child._text
300
+ f.pendingProps = child
302
301
  f.parent = parent
303
302
  return f
304
303
  }
@@ -344,44 +343,31 @@ export function reconcileChildren(
344
343
  domParent: Node,
345
344
  anchor: Node | null,
346
345
  ): void {
347
- // Fast path: unkeyed positional steady-state. The vast majority of real
348
- // re-renders hit this case (e.g. a stable list whose items shift props but
349
- // not structure). Walk the existing sibling chain and newChildren in
350
- // lockstep; if every pair matches by type and neither side has keys or
351
- // null gaps, skip the Map / Set / Array allocations and just update
352
- // pendingProps / type / ref in place. The render pass below is unchanged.
353
- // Falls back to the slow path on the first divergence.
346
+ // Fast path: unkeyed positional steady-state. Walk the existing sibling
347
+ // chain and newChildren in lockstep, validating AND committing in one pass.
348
+ // On any divergence we fall back to the slow path, which rebuilds the
349
+ // sibling chain anyway partial pendingProps writes are idempotent.
350
+ // Skips the Map / Set / existing-array allocation entirely.
354
351
  if (!currentRoot?.hydrating) {
355
352
  let f: Fiber | null = parent.child
356
- let i = 0
357
353
  let ok = true
358
- for (; i < newChildren.length; i++) {
354
+ for (let i = 0; i < newChildren.length; i++) {
359
355
  const child = newChildren[i]
360
- if (child == null) { ok = false; break }
361
- if (!f) { ok = false; break }
362
- if (f.key != null) { ok = false; break }
363
- if (!isTextChild(child) && (child as ReactElement).key != null) { ok = false; break }
364
- if (!sameType(f, child)) { ok = false; break }
356
+ if (child == null || !f || f.key != null) { ok = false; break }
357
+ if (typeof child === 'string') {
358
+ if (f.tag !== FiberTag.Text) { ok = false; break }
359
+ f.pendingProps = child
360
+ } else {
361
+ if ((child as ReactElement).key != null) { ok = false; break }
362
+ if (f.type !== (child as ReactElement).type) { ok = false; break }
363
+ f.pendingProps = (child as ReactElement).props
364
+ f.ref = (child as any).ref ?? null
365
+ }
365
366
  f = f.sibling
366
367
  }
367
368
  if (ok && f === null) {
368
- // All matched. Update each fiber's per-render state in place.
369
- let g: Fiber | null = parent.child
370
- for (let j = 0; j < newChildren.length; j++) {
371
- const child = newChildren[j]!
372
- if (isTextChild(child)) {
373
- g!.pendingProps = child._text
374
- } else {
375
- // type is already === child.type by sameType; assignment is a
376
- // no-op write but keeps the slow-path semantic that we accept
377
- // "type identity preserved" matches.
378
- g!.pendingProps = (child as ReactElement).props
379
- g!.ref = (child as any).ref ?? null
380
- }
381
- g = g!.sibling
382
- }
383
- // Pass 2: render forward with per-child anchors. This is the same loop
384
- // as the slow path's pass 2.
369
+ // Pass 2: render forward with per-child anchors. Identical to the slow
370
+ // path's pass 2.
385
371
  for (let r: Fiber | null = parent.child; r; r = r.sibling) {
386
372
  let a = anchor
387
373
  for (let s: Fiber | null = r.sibling; s; s = s.sibling) {
@@ -481,7 +467,7 @@ export function reconcileChildren(
481
467
  claimed.add(match)
482
468
  fiber = match
483
469
  if (isTextChild(child!)) {
484
- fiber.pendingProps = child._text
470
+ fiber.pendingProps = child
485
471
  } else {
486
472
  fiber.type = (child as ReactElement).type
487
473
  fiber.pendingProps = (child as ReactElement).props
@@ -725,9 +711,7 @@ export function renderFiber(fiber: Fiber, domParent: Node, anchor: Node | null):
725
711
 
726
712
  function renderText(fiber: Fiber, domParent: Node, anchor: Node | null): void {
727
713
  const text = fiber.pendingProps as string
728
- // Steady-state fast path: text identity unchanged since last render →
729
- // skip the Text.data getter (a native call) and the memoizedProps write.
730
- // Static literal-string children hit this on every re-render after mount.
714
+ // Identity-unchanged fast path: skip the native Text.data write entirely.
731
715
  if (fiber.dom && fiber.memoizedProps === text) return
732
716
  if (!fiber.dom) {
733
717
  const hydrated = currentRoot?.hydrating ? adoptTextDom(fiber, fiber.parent!, text) : false
@@ -735,7 +719,9 @@ function renderText(fiber: Fiber, domParent: Node, anchor: Node | null): void {
735
719
  fiber.dom = document.createTextNode(text)
736
720
  insertInto(domParent, fiber.dom, anchor)
737
721
  }
738
- } else if ((fiber.dom as Text).data !== text) {
722
+ } else {
723
+ // Past the fast path, and adoptTextDom already realigned `.data` on
724
+ // hydration — `.data !== text` here is guaranteed, so write directly.
739
725
  ;(fiber.dom as Text).data = text
740
726
  }
741
727
  fiber.memoizedProps = text
@@ -1088,6 +1074,12 @@ export function unmountAllChildren(parent: Fiber, domParent: Node): void {
1088
1074
  // ---------------------------------------------------------------------------
1089
1075
 
1090
1076
  function insertInto(parent: Node, node: Node, anchor: Node | null): void {
1077
+ const projectedHeadParent = getDocumentHeadInsertionParent(parent, node)
1078
+ if (projectedHeadParent) {
1079
+ projectedHeadParent.appendChild(node)
1080
+ return
1081
+ }
1082
+
1091
1083
  // Anchor may have been removed or moved since it was computed (mutations
1092
1084
  // from unmount, boundary reveal, user code, HMR). If it's no longer a child
1093
1085
  // of `parent`, fall back to append — trying to insertBefore a non-child
@@ -1099,6 +1091,15 @@ function insertInto(parent: Node, node: Node, anchor: Node | null): void {
1099
1091
  }
1100
1092
  }
1101
1093
 
1094
+ const DOCUMENT_HEAD_TAGS = new Set(['base', 'link', 'meta', 'script', 'style', 'title'])
1095
+
1096
+ function getDocumentHeadInsertionParent(parent: Node, node: Node): HTMLHeadElement | null {
1097
+ if (parent.nodeType !== 9 || node.nodeType !== 1) return null
1098
+ const tag = (node as Element).tagName.toLowerCase()
1099
+ if (!DOCUMENT_HEAD_TAGS.has(tag)) return null
1100
+ return (parent as Document).head
1101
+ }
1102
+
1102
1103
  function getHostParent(fiber: Fiber): Node {
1103
1104
  let p = fiber.parent
1104
1105
  while (p) {
@@ -1274,4 +1275,3 @@ function isEventProp(name: string): boolean {
1274
1275
  name.charCodeAt(2) >= 65 /* 'A'-ish: any uppercase start (onClick, onChange, …) */
1275
1276
  )
1276
1277
  }
1277
-
package/src/dom/root.ts CHANGED
@@ -1,4 +1,11 @@
1
- import { FiberTag, createFiber, type FiberRoot, type ReactNode } from '../core'
1
+ import {
2
+ FiberTag,
3
+ REACT_ELEMENT_TYPE,
4
+ createFiber,
5
+ type FiberRoot,
6
+ type ReactElement,
7
+ type ReactNode,
8
+ } from '../core'
2
9
  import { renderRoot, flushSyncWork, batchedUpdates } from './reconcile'
3
10
  import {
4
11
  beginHydration,
@@ -92,8 +99,10 @@ export function hydrateRoot(
92
99
 
93
100
  beginHydration(root)
94
101
  try {
102
+ const normalizedInitialChildren =
103
+ isDocumentContainer(container) ? normalizeDocumentChildren(initialChildren) : initialChildren
95
104
  flushSyncWork(() => {
96
- renderRoot(root, initialChildren)
105
+ renderRoot(root, normalizedInitialChildren)
97
106
  })
98
107
  } finally {
99
108
  endHydration(root)
@@ -103,7 +112,7 @@ export function hydrateRoot(
103
112
  return {
104
113
  render(children) {
105
114
  flushSyncWork(() => {
106
- renderRoot(root, children)
115
+ renderRoot(root, isDocumentContainer(container) ? normalizeDocumentChildren(children) : children)
107
116
  })
108
117
  },
109
118
  unmount() {
@@ -114,4 +123,91 @@ export function hydrateRoot(
114
123
  }
115
124
  }
116
125
 
126
+ const HEAD_TAGS = new Set(['base', 'link', 'meta', 'script', 'style', 'title'])
127
+
128
+ function isDocumentContainer(container: unknown): container is Document {
129
+ return !!container && (container as Node).nodeType === 9
130
+ }
131
+
132
+ function normalizeDocumentChildren(children: ReactNode): ReactNode {
133
+ const list = toChildArray(children)
134
+ const htmlIndex = list.findIndex((child) => isHostElement(child, 'html'))
135
+ if (htmlIndex === -1) return children
136
+
137
+ const headNodes = list.filter(isHeadElement)
138
+ if (headNodes.length === 0) return children
139
+
140
+ const htmlElement = list[htmlIndex] as ReactElement
141
+ const normalizedHtml = hoistIntoHtmlHead(htmlElement, headNodes)
142
+ return list
143
+ .filter((child, index) => index === htmlIndex || !isHeadElement(child))
144
+ .map((child) => (child === htmlElement ? normalizedHtml : child))
145
+ }
146
+
147
+ function hoistIntoHtmlHead(htmlElement: ReactElement, headNodes: ReactNode[]): ReactElement {
148
+ const htmlChildren = toChildArray(htmlElement.props?.children)
149
+ const headIndex = htmlChildren.findIndex((child) => isHostElement(child, 'head'))
150
+ let nextChildren: ReactNode[]
151
+
152
+ if (headIndex === -1) {
153
+ nextChildren = [
154
+ createHostElement('head', { children: headNodes }),
155
+ ...htmlChildren,
156
+ ]
157
+ } else {
158
+ const headElement = htmlChildren[headIndex] as ReactElement
159
+ const existingHeadChildren = toChildArray(headElement.props?.children)
160
+ const nextHead = {
161
+ ...headElement,
162
+ props: {
163
+ ...headElement.props,
164
+ children: [...headNodes, ...existingHeadChildren],
165
+ },
166
+ }
167
+ nextChildren = htmlChildren.map((child, index) => (index === headIndex ? nextHead : child))
168
+ }
169
+
170
+ return {
171
+ ...htmlElement,
172
+ props: {
173
+ ...htmlElement.props,
174
+ children: nextChildren,
175
+ },
176
+ }
177
+ }
178
+
179
+ function toChildArray(children: unknown): ReactNode[] {
180
+ if (children == null || typeof children === 'boolean') return []
181
+ if (Array.isArray(children)) return children as ReactNode[]
182
+ if (isReactElement(children)) return [children]
183
+ if (typeof children !== 'string' && isIterable(children)) return Array.from(children) as ReactNode[]
184
+ return [children as ReactNode]
185
+ }
186
+
187
+ function isHeadElement(value: ReactNode): boolean {
188
+ return isReactElement(value) && typeof value.type === 'string' && HEAD_TAGS.has(value.type)
189
+ }
190
+
191
+ function isHostElement(value: ReactNode, tag: string): boolean {
192
+ return isReactElement(value) && value.type === tag
193
+ }
194
+
195
+ function isReactElement(value: unknown): value is ReactElement {
196
+ return !!value && typeof value === 'object' && (value as ReactElement).$$typeof === REACT_ELEMENT_TYPE
197
+ }
198
+
199
+ function isIterable(value: unknown): value is Iterable<ReactNode> {
200
+ return !!value && typeof (value as { [Symbol.iterator]?: unknown })[Symbol.iterator] === 'function'
201
+ }
202
+
203
+ function createHostElement(type: string, props: Record<string, unknown>): ReactElement {
204
+ return {
205
+ $$typeof: REACT_ELEMENT_TYPE,
206
+ type,
207
+ key: null,
208
+ ref: null,
209
+ props,
210
+ }
211
+ }
212
+
117
213
  export { flushSyncWork as flushSync, batchedUpdates }
@@ -6,9 +6,12 @@
6
6
  * main bundle can replay them against un-hydrated subtrees ($RE_q buffer).
7
7
  *
8
8
  * Wire format:
9
- * Fallback: <!--$?ID--><div hidden id="B:ID">fallback</div><!--/$-->
9
+ * Fallback: <!--$?ID--><div id="B:ID">fallback</div><!--/$-->
10
10
  * Resolved: emits <div hidden id="S:ID">real</div><script>$RC(ID)</script>
11
11
  * which splices real into place and rewrites the comment to <!--$ID-->.
12
+ * The fallback div is visible (it's the user-visible loading
13
+ * state); only the resolved-content staging div is `hidden`
14
+ * before $RC moves its children inline.
12
15
  *
13
16
  * Client hydration calls $RH(ID, cb) to register a callback invoked once the
14
17
  * boundary has been revealed (or immediately, if it was already revealed).
@@ -8,10 +8,12 @@ import {
8
8
  } from './dispatcher'
9
9
  import { walk, type SuspendedBoundary } from './walk'
10
10
  import { BOUNDARY_REVEAL_RUNTIME, revealScript } from './bootstrap-script'
11
+ import { escapeScript } from './escape'
11
12
 
12
13
  export interface StreamOptions {
13
14
  identifierPrefix?: string
14
15
  nonce?: string
16
+ bootstrapScriptContent?: string | ReadonlyArray<string>
15
17
  bootstrapScripts?: ReadonlyArray<string | { src: string; async?: boolean; nonce?: string }>
16
18
  bootstrapModules?: ReadonlyArray<string | { src: string; nonce?: string }>
17
19
  onError?: (error: unknown) => string | void
@@ -57,12 +59,34 @@ export async function streamHtml(
57
59
  shellChunks.push(chunk)
58
60
  }
59
61
 
60
- // 1. Render the shell
61
- walk(children, {
62
- emit: bufferedEmit,
63
- onSuspend: (b) => boundaries.push(b),
64
- nextBoundaryId: () => state.nextId++,
65
- })
62
+ // 1. Render the shell. A root-level `use(promise)` has no Suspense
63
+ // boundary to capture it, but App Router SSR commonly suspends at this
64
+ // level while resolving the RSC stream. Wait and retry without leaking
65
+ // partial shell chunks or boundary ids from the aborted attempt.
66
+ let shellRendered = false
67
+ let rootRetries = 0
68
+ while (!shellRendered) {
69
+ const chunkCount = shellChunks.length
70
+ const boundaryCount = boundaries.length
71
+ const nextId = state.nextId
72
+ try {
73
+ walk(children, {
74
+ emit: bufferedEmit,
75
+ onSuspend: (b) => boundaries.push(b),
76
+ nextBoundaryId: () => state.nextId++,
77
+ })
78
+ shellRendered = true
79
+ } catch (err) {
80
+ shellChunks.length = chunkCount
81
+ boundaries.length = boundaryCount
82
+ state.nextId = nextId
83
+ if (!isThenable(err)) throw err
84
+ if (++rootRetries > 50) {
85
+ throw new Error('renderToReadableStream exceeded 50 root suspension retries.')
86
+ }
87
+ await err
88
+ }
89
+ }
66
90
 
67
91
  // 2. Inject runtime + bootstrap scripts (once, after shell). Skip the
68
92
  // reveal/event-replay runtime when nothing needs it — no suspensions to
@@ -71,10 +95,14 @@ export async function streamHtml(
71
95
  // matches React's behavior where `renderToReadableStream` of a static
72
96
  // tree emits only the markup.
73
97
  const hasBootstrap =
98
+ bootstrapScriptContentToArray(options.bootstrapScriptContent).length > 0 ||
74
99
  (options.bootstrapScripts?.length ?? 0) > 0 ||
75
100
  (options.bootstrapModules?.length ?? 0) > 0
76
101
  if (boundaries.length > 0 || hasBootstrap) {
77
102
  shellChunks.push(`<script${nonce ? ` nonce="${nonce}"` : ''}>${BOUNDARY_REVEAL_RUNTIME}</script>`)
103
+ for (const content of bootstrapScriptContentToArray(options.bootstrapScriptContent)) {
104
+ shellChunks.push(inlineBootstrapTag(content, nonce))
105
+ }
78
106
  for (const s of options.bootstrapScripts ?? []) {
79
107
  shellChunks.push(bootstrapTag(s, 'script', nonce))
80
108
  }
@@ -83,7 +111,7 @@ export async function streamHtml(
83
111
  }
84
112
  }
85
113
 
86
- if (shellChunks.length) emit(shellChunks.join(''))
114
+ if (shellChunks.length) emit(normalizeDocumentShell(shellChunks.join('')))
87
115
 
88
116
  // 3. Stream suspended boundaries as they resolve
89
117
  for (const b of boundaries) streamBoundary(b, emit, options, state)
@@ -146,6 +174,59 @@ async function drain(state: OrchestratorState): Promise<void> {
146
174
  }
147
175
  }
148
176
 
177
+ function isThenable(value: unknown): value is Promise<unknown> {
178
+ return !!value && typeof (value as { then?: unknown }).then === 'function'
179
+ }
180
+
181
+ function bootstrapScriptContentToArray(
182
+ content: StreamOptions['bootstrapScriptContent'],
183
+ ): string[] {
184
+ if (content === undefined) return []
185
+ return typeof content === 'string' ? [content] : [...content]
186
+ }
187
+
188
+ function inlineBootstrapTag(content: string, defaultNonce: string | undefined): string {
189
+ const nAttr = defaultNonce ? ` nonce="${defaultNonce}"` : ''
190
+ return `<script${nAttr}>${escapeScript(content)}</script>`
191
+ }
192
+
193
+ function normalizeDocumentShell(html: string): string {
194
+ const doctypeIndex = html.indexOf('<!DOCTYPE html><html')
195
+ if (doctypeIndex <= 0) return html
196
+
197
+ const headPrefix = html.slice(0, doctypeIndex)
198
+ if (!isHeadPrefix(headPrefix)) return html
199
+
200
+ const documentHtml = html.slice(doctypeIndex)
201
+ const headOpen = documentHtml.match(/<head(?:\s[^>]*)?>/)
202
+ if (!headOpen || headOpen.index === undefined) return html
203
+
204
+ const insertAt = headOpen.index + headOpen[0].length
205
+ return documentHtml.slice(0, insertAt) + headPrefix + documentHtml.slice(insertAt)
206
+ }
207
+
208
+ function isHeadPrefix(value: string): boolean {
209
+ if (!value) return false
210
+ return stripLeadingHeadTags(value).trim() === ''
211
+ }
212
+
213
+ function stripLeadingHeadTags(value: string): string {
214
+ let rest = value
215
+ let changed = true
216
+ while (changed) {
217
+ changed = false
218
+ const next = rest.replace(
219
+ /^\s*(?:<meta\b[^>]*>|<link\b[^>]*>|<base\b[^>]*>|<title\b[^>]*>[\s\S]*?<\/title>|<style\b[^>]*>[\s\S]*?<\/style>|<script\b[^>]*>[\s\S]*?<\/script>)/i,
220
+ '',
221
+ )
222
+ if (next !== rest) {
223
+ rest = next
224
+ changed = true
225
+ }
226
+ }
227
+ return rest
228
+ }
229
+
149
230
  function bootstrapTag(
150
231
  entry: string | { src: string; async?: boolean; nonce?: string },
151
232
  kind: 'script' | 'module',
@@ -315,6 +315,10 @@ function walkHost(
315
315
  const parentTextState = opts.textState
316
316
  const childOpts: WalkOptions = { ...opts, textState: { lastWasText: false } }
317
317
 
318
+ if (tag === 'html' && !hasHeadChild(props.children)) {
319
+ opts.emit('<head></head>')
320
+ }
321
+
318
322
  const dangerouslyHtml = props.dangerouslySetInnerHTML?.__html
319
323
 
320
324
  if (isTextarea && textareaValue != null) {
@@ -353,6 +357,32 @@ function walkHost(
353
357
  if (parentTextState) parentTextState.lastWasText = false
354
358
  }
355
359
 
360
+ function hasHeadChild(children: unknown): boolean {
361
+ if (children == null || typeof children === 'boolean') return false
362
+ if (Array.isArray(children)) return children.some(hasHeadChild)
363
+ if (typeof children !== 'string' && isIterable(children)) {
364
+ for (const child of children as Iterable<unknown>) {
365
+ if (hasHeadChild(child)) return true
366
+ }
367
+ return false
368
+ }
369
+ return isElementOfType(children, 'head')
370
+ }
371
+
372
+ function isElementOfType(value: unknown, type: string): value is ReactElement {
373
+ const marker = (value as ReactElement | null)?.$$typeof as unknown
374
+ return (
375
+ !!value &&
376
+ typeof value === 'object' &&
377
+ (marker === REACT_ELEMENT_TYPE || marker === REACT_LEGACY_ELEMENT_TYPE) &&
378
+ (value as ReactElement).type === type
379
+ )
380
+ }
381
+
382
+ function isIterable(value: unknown): value is Iterable<unknown> {
383
+ return !!value && typeof (value as { [Symbol.iterator]?: unknown })[Symbol.iterator] === 'function'
384
+ }
385
+
356
386
  function walkComponent(
357
387
  fn: Function,
358
388
  props: Record<string, any>,
package/src/vite/index.ts CHANGED
@@ -166,6 +166,8 @@ const ALIASES: Record<string, string> = {
166
166
  'react/jsx-dev-runtime': '@tanstack/redact/jsx-dev-runtime',
167
167
  'react/compiler-runtime': '@tanstack/redact/compiler-runtime',
168
168
  'react-dom/client': '@tanstack/redact/dom-client',
169
+ 'react-dom/server.edge': '@tanstack/redact/server',
170
+ 'react-dom/static.edge': '@tanstack/redact/server',
169
171
  'react-dom/server': '@tanstack/redact/server',
170
172
  'react-dom/test-utils': '@tanstack/redact/dom-test-utils',
171
173
  'react-dom': '@tanstack/redact/dom',