@@ -1526,6 +1526,19 @@ const startClientRenderedSuspenseBoundary = stringToPrecomputedChunk(
1526
1526
) ;
1527
1527
const endSuspenseBoundary = stringToPrecomputedChunk ( '<!--/$-->' ) ;
1528
1528
1529
+ const clientRenderedSuspenseBoundaryError1 = stringToPrecomputedChunk (
1530
+ '<template data-hash="' ,
1531
+ ) ;
1532
+ const clientRenderedSuspenseBoundaryError1A = stringToPrecomputedChunk (
1533
+ '" data-msg="' ,
1534
+ ) ;
1535
+ const clientRenderedSuspenseBoundaryError1B = stringToPrecomputedChunk (
1536
+ '" data-stack="' ,
1537
+ ) ;
1538
+ const clientRenderedSuspenseBoundaryError2 = stringToPrecomputedChunk (
1539
+ '"></template>' ,
1540
+ ) ;
1541
+
1529
1542
export function pushStartCompletedSuspenseBoundary (
1530
1543
target : Array < Chunk | PrecomputedChunk > ,
1531
1544
) {
@@ -1563,8 +1576,43 @@ export function writeStartPendingSuspenseBoundary(
1563
1576
export function writeStartClientRenderedSuspenseBoundary (
1564
1577
destination : Destination ,
1565
1578
responseState : ResponseState ,
1579
+ errorHash : ?string ,
1580
+ errorMesssage : ?string ,
1581
+ errorComponentStack : ?string ,
1566
1582
) : boolean {
1567
- return writeChunkAndReturn ( destination , startClientRenderedSuspenseBoundary ) ;
1583
+ let result ;
1584
+ result = writeChunkAndReturn (
1585
+ destination ,
1586
+ startClientRenderedSuspenseBoundary ,
1587
+ ) ;
1588
+ if ( errorHash ) {
1589
+ writeChunk ( destination , clientRenderedSuspenseBoundaryError1 ) ;
1590
+ writeChunk ( destination , stringToChunk ( escapeTextForBrowser ( errorHash ) ) ) ;
1591
+ // In prod errorMessage will usually be nullish but there is one case where
1592
+ // it is used (currently when the server aborts the task) so we leave it ungated.
1593
+ if ( errorMesssage ) {
1594
+ writeChunk ( destination , clientRenderedSuspenseBoundaryError1A ) ;
1595
+ writeChunk (
1596
+ destination ,
1597
+ stringToChunk ( escapeTextForBrowser ( errorMesssage ) ) ,
1598
+ ) ;
1599
+ }
1600
+ if ( __DEV__ ) {
1601
+ // Component stacks are currently only captured in dev
1602
+ if ( errorComponentStack ) {
1603
+ writeChunk ( destination , clientRenderedSuspenseBoundaryError1B ) ;
1604
+ writeChunk (
1605
+ destination ,
1606
+ stringToChunk ( escapeTextForBrowser ( errorComponentStack ) ) ,
1607
+ ) ;
1608
+ }
1609
+ }
1610
+ result = writeChunkAndReturn (
1611
+ destination ,
1612
+ clientRenderedSuspenseBoundaryError2 ,
1613
+ ) ;
1614
+ }
1615
+ return result ;
1568
1616
}
1569
1617
export function writeEndCompletedSuspenseBoundary (
1570
1618
destination : Destination ,
@@ -1724,7 +1772,7 @@ export function writeEndSegment(
1724
1772
// const SUSPENSE_PENDING_START_DATA = '$?';
1725
1773
// const SUSPENSE_FALLBACK_START_DATA = '$!';
1726
1774
//
1727
- // function clientRenderBoundary(suspenseBoundaryID) {
1775
+ // function clientRenderBoundary(suspenseBoundaryID, errorHash, errorMsg, errorComponentStack ) {
1728
1776
// // Find the fallback's first element.
1729
1777
// const suspenseIdNode = document.getElementById(suspenseBoundaryID);
1730
1778
// if (!suspenseIdNode) {
@@ -1736,6 +1784,11 @@ export function writeEndSegment(
1736
1784
// const suspenseNode = suspenseIdNode.previousSibling;
1737
1785
// // Tag it to be client rendered.
1738
1786
// suspenseNode.data = SUSPENSE_FALLBACK_START_DATA;
1787
+ // // assign error metadata to first sibling
1788
+ // let dataset = suspenseIdNode.dataset;
1789
+ // if (errorHash) dataset.hash = errorHash;
1790
+ // if (errorMsg) dataset.msg = errorMsg;
1791
+ // if (errorComponentStack) dataset.stack = errorComponentStack;
1739
1792
// // Tell React to retry it if the parent already hydrated.
1740
1793
// if (suspenseNode._reactRetry) {
1741
1794
// suspenseNode._reactRetry();
@@ -1823,7 +1876,7 @@ const completeSegmentFunction =
1823
1876
const completeBoundaryFunction =
1824
1877
'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()}}' ;
1825
1878
const clientRenderFunction =
1826
- 'function $RX(a){if( a=document.getElementById(a))a =a.previousSibling,a .data="$!",a._reactRetry&&a. _reactRetry( )}' ;
1879
+ '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() )}' ;
1827
1880
1828
1881
const completeSegmentScript1Full = stringToPrecomputedChunk (
1829
1882
completeSegmentFunction + ';$RS("' ,
@@ -1896,12 +1949,17 @@ const clientRenderScript1Full = stringToPrecomputedChunk(
1896
1949
clientRenderFunction + ';$RX("' ,
1897
1950
) ;
1898
1951
const clientRenderScript1Partial = stringToPrecomputedChunk ( '$RX("' ) ;
1899
- const clientRenderScript2 = stringToPrecomputedChunk ( '")</script>' ) ;
1952
+ const clientRenderScript1A = stringToPrecomputedChunk ( '"' ) ;
1953
+ const clientRenderScript2 = stringToPrecomputedChunk ( ')</script>' ) ;
1954
+ const clientRenderErrorScriptArgInterstitial = stringToPrecomputedChunk ( ',' ) ;
1900
1955
1901
1956
export function writeClientRenderBoundaryInstruction (
1902
1957
destination : Destination ,
1903
1958
responseState : ResponseState ,
1904
1959
boundaryID : SuspenseBoundaryID ,
1960
+ errorHash : ?string ,
1961
+ errorMessage ?: string ,
1962
+ errorComponentStack ?: string ,
1905
1963
) : boolean {
1906
1964
writeChunk ( destination , responseState . startInlineScript ) ;
1907
1965
if ( ! responseState . sentClientRenderFunction ) {
@@ -1920,5 +1978,49 @@ export function writeClientRenderBoundaryInstruction(
1920
1978
}
1921
1979
1922
1980
writeChunk ( destination , boundaryID ) ;
1981
+ writeChunk ( destination , clientRenderScript1A ) ;
1982
+ if ( errorHash || errorMessage || errorComponentStack ) {
1983
+ writeChunk ( destination , clientRenderErrorScriptArgInterstitial ) ;
1984
+ writeChunk (
1985
+ destination ,
1986
+ stringToChunk ( escapeJSStringsForInstructionScripts ( errorHash || '' ) ) ,
1987
+ ) ;
1988
+ }
1989
+ if ( errorMessage || errorComponentStack ) {
1990
+ writeChunk ( destination , clientRenderErrorScriptArgInterstitial ) ;
1991
+ writeChunk (
1992
+ destination ,
1993
+ stringToChunk ( escapeJSStringsForInstructionScripts ( errorMessage || '' ) ) ,
1994
+ ) ;
1995
+ }
1996
+ if ( errorComponentStack ) {
1997
+ writeChunk ( destination , clientRenderErrorScriptArgInterstitial ) ;
1998
+ writeChunk (
1999
+ destination ,
2000
+ stringToChunk ( escapeJSStringsForInstructionScripts ( errorComponentStack ) ) ,
2001
+ ) ;
2002
+ }
1923
2003
return writeChunkAndReturn ( destination , clientRenderScript2 ) ;
1924
2004
}
2005
+
2006
+ const regexForJSStringsInScripts = / [ < \u2028 \u2029 ] / g;
2007
+ function escapeJSStringsForInstructionScripts ( input : string ) : string {
2008
+ const escaped = JSON . stringify ( input ) ;
2009
+ return escaped . replace ( regexForJSStringsInScripts , match => {
2010
+ switch ( match ) {
2011
+ // santizing breaking out of strings and script tags
2012
+ case '<' :
2013
+ return '\\u003c' ;
2014
+ case '\u2028' :
2015
+ return '\\u2028' ;
2016
+ case '\u2029' :
2017
+ return '\\u2029' ;
2018
+ default : {
2019
+ // eslint-disable-next-line react-internal/prod-error-codes
2020
+ throw new Error (
2021
+ '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' ,
2022
+ ) ;
2023
+ }
2024
+ }
2025
+ } ) ;
2026
+ }
0 commit comments