@@ -266,10 +266,6 @@ const COMPLETED = 1;
266
266
const ABORTED = 3 ;
267
267
const ERRORED = 4 ;
268
268
269
- // object reference status
270
- const SEEN_BUT_NOT_YET_OUTLINED = - 1 ;
271
- const NEVER_OUTLINED = - 2 ;
272
-
273
269
type Task = {
274
270
id : number ,
275
271
status : 0 | 1 | 3 | 4 ,
@@ -303,7 +299,7 @@ export type Request = {
303
299
writtenSymbols : Map < symbol , number> ,
304
300
writtenClientReferences : Map < ClientReferenceKey , number> ,
305
301
writtenServerReferences : Map < ServerReference < any > , number > ,
306
- writtenObjects : WeakMap < Reference , number > ,
302
+ writtenObjects : WeakMap < Reference , string > ,
307
303
identifierPrefix : string ,
308
304
identifierCount : number ,
309
305
taintCleanupQueue : Array < string | bigint > ,
@@ -1270,7 +1266,7 @@ function createTask(
1270
1266
// If we're in some kind of context we can't necessarily reuse this object depending
1271
1267
// what parent components are used.
1272
1268
} else {
1273
- request . writtenObjects . set ( model , id ) ;
1269
+ request . writtenObjects . set ( model , serializeByValueID ( id ) ) ;
1274
1270
}
1275
1271
}
1276
1272
const task : Task = {
@@ -1511,16 +1507,6 @@ function serializeMap(
1511
1507
map : Map < ReactClientValue , ReactClientValue > ,
1512
1508
) : string {
1513
1509
const entries = Array . from ( map ) ;
1514
- for ( let i = 0 ; i < entries . length ; i ++ ) {
1515
- const key = entries [ i ] [ 0 ] ;
1516
- if ( typeof key === 'object' && key !== null ) {
1517
- const writtenObjects = request . writtenObjects ;
1518
- const existingId = writtenObjects . get ( key ) ;
1519
- if ( existingId === undefined ) {
1520
- writtenObjects . set ( key , SEEN_BUT_NOT_YET_OUTLINED ) ;
1521
- }
1522
- }
1523
- }
1524
1510
const id = outlineModel ( request , entries ) ;
1525
1511
return '$Q' + id . toString ( 16 ) ;
1526
1512
}
@@ -1533,16 +1519,6 @@ function serializeFormData(request: Request, formData: FormData): string {
1533
1519
1534
1520
function serializeSet ( request : Request , set : Set < ReactClientValue > ) : string {
1535
1521
const entries = Array . from ( set ) ;
1536
- for ( let i = 0 ; i < entries . length ; i ++ ) {
1537
- const key = entries [ i ] ;
1538
- if ( typeof key === 'object' && key !== null ) {
1539
- const writtenObjects = request . writtenObjects ;
1540
- const existingId = writtenObjects . get ( key ) ;
1541
- if ( existingId === undefined ) {
1542
- writtenObjects . set ( key , SEEN_BUT_NOT_YET_OUTLINED ) ;
1543
- }
1544
- }
1545
- }
1546
1522
const id = outlineModel ( request , entries ) ;
1547
1523
return '$W' + id . toString ( 16 ) ;
1548
1524
}
@@ -1754,42 +1730,42 @@ function renderModelDestructive(
1754
1730
switch ( ( value : any ) . $$typeof ) {
1755
1731
case REACT_ELEMENT_TYPE : {
1756
1732
const writtenObjects = request . writtenObjects ;
1757
- const existingId = writtenObjects . get ( value ) ;
1758
- if ( existingId !== undefined ) {
1759
- if (
1760
- enableServerComponentKeys &&
1761
- ( task . keyPath !== null || task . implicitSlot )
1762
- ) {
1763
- // If we're in some kind of context we can't reuse the result of this render or
1764
- // previous renders of this element. We only reuse elements if they're not wrapped
1765
- // by another Server Component.
1766
- } else if ( modelRoot === value ) {
1767
- // This is the ID we're currently emitting so we need to write it
1768
- // once but if we discover it again, we refer to it by id.
1769
- modelRoot = null ;
1770
- } else if ( existingId === SEEN_BUT_NOT_YET_OUTLINED ) {
1771
- // TODO: If we throw here we can treat this as suspending which causes an outline
1772
- // but that is able to reuse the same task if we're already in one but then that
1773
- // will be a lazy future value rather than guaranteed to exist but maybe that's good.
1774
- const newId = outlineModel ( request , ( value : any ) ) ;
1775
- return serializeByValueID ( newId ) ;
1776
- } else {
1777
- // We've already emitted this as an outlined object, so we can refer to that by its
1778
- // existing ID. TODO: We should use a lazy reference since, unlike plain objects,
1779
- // elements might suspend so it might not have emitted yet even if we have the ID for
1780
- // it. However, this creates an extra wrapper when it's not needed. We should really
1781
- // detect whether this already was emitted and synchronously available. In that
1782
- // case we can refer to it synchronously and only make it lazy otherwise.
1783
- // We currently don't have a data structure that lets us see that though.
1784
- return serializeByValueID ( existingId ) ;
1785
- }
1733
+ if (
1734
+ enableServerComponentKeys &&
1735
+ ( task . keyPath !== null || task . implicitSlot )
1736
+ ) {
1737
+ // If we're in some kind of context we can't reuse the result of this render or
1738
+ // previous renders of this element. We only reuse elements if they're not wrapped
1739
+ // by another Server Component.
1786
1740
} else {
1787
- // This is the first time we've seen this object. We may never see it again
1788
- // so we'll inline it. Mark it as seen. If we see it again, we'll outline.
1789
- writtenObjects . set ( value , SEEN_BUT_NOT_YET_OUTLINED ) ;
1790
- // The element's props are marked as "never outlined" so that they are inlined into
1791
- // the same row as the element itself.
1792
- writtenObjects . set ( ( value : any ) . props , NEVER_OUTLINED ) ;
1741
+ const existingReference = writtenObjects . get ( value ) ;
1742
+ if ( existingReference !== undefined ) {
1743
+ if ( modelRoot === value ) {
1744
+ // This is the ID we're currently emitting so we need to write it
1745
+ // once but if we discover it again, we refer to it by id.
1746
+ modelRoot = null ;
1747
+ } else {
1748
+ // We've already emitted this as an outlined object, so we can refer to that by its
1749
+ // existing ID. TODO: We should use a lazy reference since, unlike plain objects,
1750
+ // elements might suspend so it might not have emitted yet even if we have the ID for
1751
+ // it. However, this creates an extra wrapper when it's not needed. We should really
1752
+ // detect whether this already was emitted and synchronously available. In that
1753
+ // case we can refer to it synchronously and only make it lazy otherwise.
1754
+ // We currently don't have a data structure that lets us see that though.
1755
+ return existingReference ;
1756
+ }
1757
+ } else if ( parentPropertyName . indexOf ( ':' ) === - 1 ) {
1758
+ // TODO: If the property name contains a colon, we don't dedupe. Escape instead.
1759
+ const parentReference = writtenObjects . get ( parent ) ;
1760
+ if ( parentReference !== undefined ) {
1761
+ // If the parent has a reference, we can refer to this object indirectly
1762
+ // through the property name inside that parent.
1763
+ writtenObjects . set (
1764
+ value ,
1765
+ parentReference + ':' + parentPropertyName ,
1766
+ ) ;
1767
+ }
1768
+ }
1793
1769
}
1794
1770
1795
1771
const element : ReactElement = ( value : any ) ;
@@ -1885,10 +1861,10 @@ function renderModelDestructive(
1885
1861
}
1886
1862
1887
1863
const writtenObjects = request . writtenObjects ;
1888
- const existingId = writtenObjects . get ( value ) ;
1864
+ const existingReference = writtenObjects . get ( value ) ;
1889
1865
// $FlowFixMe[method-unbinding]
1890
1866
if ( typeof value . then === 'function' ) {
1891
- if ( existingId !== undefined ) {
1867
+ if ( existingReference !== undefined ) {
1892
1868
if (
1893
1869
enableServerComponentKeys &&
1894
1870
( task . keyPath !== null || task . implicitSlot )
@@ -1904,33 +1880,48 @@ function renderModelDestructive(
1904
1880
modelRoot = null ;
1905
1881
} else {
1906
1882
// We've seen this promise before, so we can just refer to the same result.
1907
- return serializePromiseID ( existingId ) ;
1883
+ return ' $ @' + existingReference . slice ( 1 ) ;
1908
1884
}
1909
1885
}
1910
1886
// We assume that any object with a .then property is a "Thenable" type,
1911
1887
// or a Promise type. Either of which can be represented by a Promise.
1912
1888
const promiseId = serializeThenable ( request , task , ( value : any ) ) ;
1913
- writtenObjects . set ( value , promiseId ) ;
1889
+ writtenObjects . set ( value , serializeByValueID ( promiseId ) ) ;
1914
1890
return serializePromiseID ( promiseId ) ;
1915
1891
}
1916
1892
1917
- if ( existingId !== undefined ) {
1893
+ if ( existingReference !== undefined ) {
1918
1894
if ( modelRoot === value ) {
1919
1895
// This is the ID we're currently emitting so we need to write it
1920
1896
// once but if we discover it again, we refer to it by id.
1921
1897
modelRoot = null ;
1922
- } else if ( existingId === SEEN_BUT_NOT_YET_OUTLINED ) {
1923
- const newId = outlineModel ( request , ( value : any ) ) ;
1924
- return serializeByValueID ( newId ) ;
1925
- } else if ( existingId !== NEVER_OUTLINED ) {
1898
+ } else {
1926
1899
// We've already emitted this as an outlined object, so we can
1927
1900
// just refer to that by its existing ID.
1928
- return serializeByValueID ( existingId ) ;
1901
+ return existingReference ;
1902
+ }
1903
+ } else if ( parentPropertyName . indexOf ( ':' ) === - 1 ) {
1904
+ // TODO: If the property name contains a colon, we don't dedupe. Escape instead.
1905
+ const parentReference = writtenObjects . get ( parent ) ;
1906
+ if ( parentReference !== undefined ) {
1907
+ // If the parent has a reference, we can refer to this object indirectly
1908
+ // through the property name inside that parent.
1909
+ let propertyName = parentPropertyName ;
1910
+ if ( isArray ( parent ) && parent [ 0 ] === REACT_ELEMENT_TYPE ) {
1911
+ // For elements, we've converted it to an array but we'll have converted
1912
+ // it back to an element before we read the references so the property
1913
+ // needs to be aliased.
1914
+ switch ( parentPropertyName ) {
1915
+ case '1 ':
1916
+ propertyName = 'type ';
1917
+ case '2 ':
1918
+ propertyName = 'key ';
1919
+ case '3 ':
1920
+ propertyName = 'props ';
1921
+ }
1922
+ }
1923
+ writtenObjects . set ( value , parentReference + ':' + propertyName ) ;
1929
1924
}
1930
- } else {
1931
- // This is the first time we've seen this object. We may never see it again
1932
- // so we'll inline it. Mark it as seen. If we see it again, we'll outline.
1933
- writtenObjects . set ( value , SEEN_BUT_NOT_YET_OUTLINED ) ;
1934
1925
}
1935
1926
1936
1927
if ( isArray ( value ) ) {
@@ -2497,12 +2488,12 @@ function renderConsoleValue(
2497
2488
counter.objectCount++;
2498
2489
2499
2490
const writtenObjects = request.writtenObjects;
2500
- const existingId = writtenObjects.get(value);
2491
+ const existingReference = writtenObjects.get(value);
2501
2492
// $FlowFixMe[method-unbinding]
2502
2493
if (typeof value.then === 'function') {
2503
- if ( existingId !== undefined ) {
2494
+ if ( existingReference !== undefined ) {
2504
2495
// We've seen this promise before, so we can just refer to the same result.
2505
- return serializePromiseID ( existingId ) ;
2496
+ return '$@' + existingReference . slice ( 1 ) ;
2506
2497
}
2507
2498
2508
2499
const thenable: Thenable< any > = (value: any);
@@ -2538,10 +2529,10 @@ function renderConsoleValue(
2538
2529
return serializeInfinitePromise();
2539
2530
}
2540
2531
2541
- if ( existingId !== undefined && existingId >= 0 ) {
2532
+ if ( existingReference !== undefined ) {
2542
2533
// We've already emitted this as a real object, so we can
2543
- // just refer to that by its existing ID .
2544
- return serializeByValueID ( existingId ) ;
2534
+ // just refer to that by its existing reference .
2535
+ return existingReference ;
2545
2536
}
2546
2537
2547
2538
if (isArray(value)) {
@@ -2933,6 +2924,10 @@ function retryTask(request: Request, task: Task): void {
2933
2924
task.implicitSlot = false;
2934
2925
2935
2926
if (typeof resolvedModel === 'object' && resolvedModel !== null ) {
2927
+ // We're not in a contextual place here so we can refer to this object by this ID for
2928
+ // any future references.
2929
+ request . writtenObjects . set ( resolvedModel , serializeByValueID ( task . id ) ) ;
2930
+
2936
2931
// Object might contain unresolved values like additional elements.
2937
2932
// This is simulating what the JSON loop would do if this was part of it.
2938
2933
emitChunk ( request , task , resolvedModel ) ;
0 commit comments