@@ -1503,6 +1503,19 @@ const startClientRenderedSuspenseBoundary = stringToPrecomputedChunk(
1503
1503
) ;
1504
1504
const endSuspenseBoundary = stringToPrecomputedChunk ( '<!--/$-->' ) ;
1505
1505
1506
+ const clientRenderedSuspenseBoundaryError1 = stringToPrecomputedChunk (
1507
+ '<template data-hash="' ,
1508
+ ) ;
1509
+ const clientRenderedSuspenseBoundaryError1A = stringToPrecomputedChunk (
1510
+ '" data-msg="' ,
1511
+ ) ;
1512
+ const clientRenderedSuspenseBoundaryError1B = stringToPrecomputedChunk (
1513
+ '" data-stack="' ,
1514
+ ) ;
1515
+ const clientRenderedSuspenseBoundaryError2 = stringToPrecomputedChunk (
1516
+ '"></template>' ,
1517
+ ) ;
1518
+
1506
1519
export function pushStartCompletedSuspenseBoundary (
1507
1520
target : Array < Chunk | PrecomputedChunk > ,
1508
1521
) {
@@ -1540,8 +1553,43 @@ export function writeStartPendingSuspenseBoundary(
1540
1553
export function writeStartClientRenderedSuspenseBoundary (
1541
1554
destination : Destination ,
1542
1555
responseState : ResponseState ,
1556
+ errorHash : ?string ,
1557
+ errorMesssage : ?string ,
1558
+ errorComponentStack : ?string ,
1543
1559
) : boolean {
1544
- return writeChunkAndReturn ( destination , startClientRenderedSuspenseBoundary ) ;
1560
+ let result ;
1561
+ result = writeChunkAndReturn (
1562
+ destination ,
1563
+ startClientRenderedSuspenseBoundary ,
1564
+ ) ;
1565
+ if ( errorHash ) {
1566
+ writeChunk ( destination , clientRenderedSuspenseBoundaryError1 ) ;
1567
+ writeChunk ( destination , stringToChunk ( escapeTextForBrowser ( errorHash ) ) ) ;
1568
+ // In prod errorMessage will usually be nullish but there is one case where
1569
+ // it is used (currently when the server aborts the task) so we leave it ungated.
1570
+ if ( errorMesssage ) {
1571
+ writeChunk ( destination , clientRenderedSuspenseBoundaryError1A ) ;
1572
+ writeChunk (
1573
+ destination ,
1574
+ stringToChunk ( escapeTextForBrowser ( errorMesssage ) ) ,
1575
+ ) ;
1576
+ }
1577
+ if ( __DEV__ ) {
1578
+ // Component stacks are currently only captured in dev
1579
+ if ( errorComponentStack ) {
1580
+ writeChunk ( destination , clientRenderedSuspenseBoundaryError1B ) ;
1581
+ writeChunk (
1582
+ destination ,
1583
+ stringToChunk ( escapeTextForBrowser ( errorComponentStack ) ) ,
1584
+ ) ;
1585
+ }
1586
+ }
1587
+ result = writeChunkAndReturn (
1588
+ destination ,
1589
+ clientRenderedSuspenseBoundaryError2 ,
1590
+ ) ;
1591
+ }
1592
+ return result ;
1545
1593
}
1546
1594
export function writeEndCompletedSuspenseBoundary (
1547
1595
destination : Destination ,
@@ -1701,7 +1749,7 @@ export function writeEndSegment(
1701
1749
// const SUSPENSE_PENDING_START_DATA = '$?';
1702
1750
// const SUSPENSE_FALLBACK_START_DATA = '$!';
1703
1751
//
1704
- // function clientRenderBoundary(suspenseBoundaryID, errorMsg) {
1752
+ // function clientRenderBoundary(suspenseBoundaryID, errorHash, errorMsg, errorComponentStack ) {
1705
1753
// // Find the fallback's first element.
1706
1754
// const suspenseIdNode = document.getElementById(suspenseBoundaryID);
1707
1755
// if (!suspenseIdNode) {
@@ -1713,7 +1761,11 @@ export function writeEndSegment(
1713
1761
// const suspenseNode = suspenseIdNode.previousSibling;
1714
1762
// // Tag it to be client rendered.
1715
1763
// suspenseNode.data = SUSPENSE_FALLBACK_START_DATA;
1716
- // suspenseNode.data2 = errorMsg;
1764
+ // // assign error metadata to first sibling
1765
+ // let dataset = suspenseIdNode.dataset;
1766
+ // if (errorHash) dataset.hash = errorHash;
1767
+ // if (errorMsg) dataset.msg = errorMsg;
1768
+ // if (errorComponentStack) dataset.stack = errorComponentStack;
1717
1769
// // Tell React to retry it if the parent already hydrated.
1718
1770
// if (suspenseNode._reactRetry) {
1719
1771
// suspenseNode._reactRetry();
@@ -1801,7 +1853,7 @@ const completeSegmentFunction =
1801
1853
const completeBoundaryFunction =
1802
1854
'function $RC(a,b){a=document.getElementById(a);b=document.getElementById(b);b.parentNode.removeChild(b);if(a){a=a.previousSibling;var f=a.parentNode,c=a.nextSibling,e=0;do{if(c&&8===c.nodeType){var d=c.data;if("/$"===d)if(0===e)break;else e--;else"$"!==d&&"$?"!==d&&"$!"!==d||e++}d=c.nextSibling;f.removeChild(c);c=d}while(c);for(;b.firstChild;)f.insertBefore(b.firstChild,c);a.data="$";a._reactRetry&&a._reactRetry()}}' ;
1803
1855
const clientRenderFunction =
1804
- 'function $RX(a,b){if( a=document.getElementById(a))a =a.previousSibling,a .data="$!",a.data2=b,a._reactRetry&&a. _reactRetry( )}' ;
1856
+ 'function $RX(b,c,d,e){var a=document.getElementById(b);a&&(b =a.previousSibling,b .data="$!",a=a.dataset,c&&(a.hash=c),d&&(a.msg=d),e&&(a.stack=e),b. _reactRetry&&b._reactRetry() )}' ;
1805
1857
1806
1858
const completeSegmentScript1Full = stringToPrecomputedChunk (
1807
1859
completeSegmentFunction + ';$RS("' ,
@@ -1871,17 +1923,20 @@ export function writeCompletedBoundaryInstruction(
1871
1923
}
1872
1924
1873
1925
const clientRenderScript1Full = stringToPrecomputedChunk (
1874
- clientRenderFunction + " ;$RX('" ,
1926
+ clientRenderFunction + ' ;$RX("' ,
1875
1927
) ;
1876
- const clientRenderScript1Partial = stringToPrecomputedChunk ( "$RX('" ) ;
1877
- const clientRenderScript2 = stringToPrecomputedChunk ( "')</script>" ) ;
1878
- const clientRenderErrorScript1 = stringToPrecomputedChunk ( "','" ) ;
1928
+ const clientRenderScript1Partial = stringToPrecomputedChunk ( '$RX("' ) ;
1929
+ const clientRenderScript1A = stringToPrecomputedChunk ( '"' ) ;
1930
+ const clientRenderScript2 = stringToPrecomputedChunk ( ')</script>' ) ;
1931
+ const clientRenderErrorScriptArgInterstitial = stringToPrecomputedChunk ( ',' ) ;
1879
1932
1880
1933
export function writeClientRenderBoundaryInstruction (
1881
1934
destination : Destination ,
1882
1935
responseState : ResponseState ,
1883
1936
boundaryID : SuspenseBoundaryID ,
1884
- error : ?string ,
1937
+ errorHash : ?string ,
1938
+ errorMessage ?: string ,
1939
+ errorComponentStack ?: string ,
1885
1940
) : boolean {
1886
1941
writeChunk ( destination , responseState . startInlineScript ) ;
1887
1942
if ( ! responseState . sentClientRenderFunction ) {
@@ -1900,9 +1955,49 @@ export function writeClientRenderBoundaryInstruction(
1900
1955
}
1901
1956
1902
1957
writeChunk ( destination , boundaryID ) ;
1903
- if ( error ) {
1904
- writeChunk ( destination , clientRenderErrorScript1 ) ;
1905
- writeChunk ( destination , stringToChunk ( error ) ) ;
1958
+ writeChunk ( destination , clientRenderScript1A ) ;
1959
+ if ( errorHash || errorMessage || errorComponentStack ) {
1960
+ writeChunk ( destination , clientRenderErrorScriptArgInterstitial ) ;
1961
+ writeChunk (
1962
+ destination ,
1963
+ stringToChunk ( escapeJSStringsForInstructionScripts ( errorHash || '' ) ) ,
1964
+ ) ;
1965
+ }
1966
+ if ( errorMessage || errorComponentStack ) {
1967
+ writeChunk ( destination , clientRenderErrorScriptArgInterstitial ) ;
1968
+ writeChunk (
1969
+ destination ,
1970
+ stringToChunk ( escapeJSStringsForInstructionScripts ( errorMessage || '' ) ) ,
1971
+ ) ;
1972
+ }
1973
+ if ( errorComponentStack ) {
1974
+ writeChunk ( destination , clientRenderErrorScriptArgInterstitial ) ;
1975
+ writeChunk (
1976
+ destination ,
1977
+ stringToChunk ( escapeJSStringsForInstructionScripts ( errorComponentStack ) ) ,
1978
+ ) ;
1906
1979
}
1907
1980
return writeChunkAndReturn ( destination , clientRenderScript2 ) ;
1908
1981
}
1982
+
1983
+ const regexForJSStringsInScripts = / [ < \u2028 \u2029 ] / g;
1984
+ function escapeJSStringsForInstructionScripts ( input : string ) : string {
1985
+ const escaped = JSON . stringify ( input ) ;
1986
+ return escaped . replace ( regexForJSStringsInScripts , match => {
1987
+ switch ( match ) {
1988
+ // santizing breaking out of strings and script tags
1989
+ case '<' :
1990
+ return '\\u003c' ;
1991
+ case '\u2028' :
1992
+ return '\\u2028' ;
1993
+ case '\u2029' :
1994
+ return '\\u2029' ;
1995
+ default : {
1996
+ // eslint-disable-next-line react-internal/prod-error-codes
1997
+ throw new Error (
1998
+ 'escapeJSStringsForInstructionScripts encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React' ,
1999
+ ) ;
2000
+ }
2001
+ }
2002
+ } ) ;
2003
+ }
0 commit comments