@@ -847,7 +847,7 @@ export const supportsHydration = true;
847
847
848
848
// With Resources, some HostComponent types will never be server rendered and need to be
849
849
// inserted without breaking hydration
850
- export function isHydratable ( type : string , props : Props ) : boolean {
850
+ export function isHydratableType ( type : string , props : Props ) : boolean {
851
851
if ( enableFloat ) {
852
852
if ( type === 'script' ) {
853
853
const { async , onLoad , onError } = ( props : any ) ;
@@ -858,211 +858,167 @@ export function isHydratable(type: string, props: Props): boolean {
858
858
return true ;
859
859
}
860
860
}
861
-
862
- // In certain contexts, namely <body> and <head>, we want to skip past Nodes that are in theory
863
- // hydratable but do not match the current Fiber being hydrated. We track the hydratable node we
864
- // are currently attempting using this module global. If the hydration is unsuccessful Fiber will
865
- // call getLastAttemptedHydratable which uses this cursor to return the expected next
866
- // hydratable.
867
- let hydratableNode : null | HydratableInstance = null ;
868
-
869
- export function getLastAttemptedHydratable ( ) : null | HydratableInstance {
870
- return hydratableNode ;
861
+ export function isHydratableText ( text : string ) : boolean {
862
+ return text !== '' ;
871
863
}
872
864
873
- export function getNextMatchingHydratableInstance (
865
+ export function shouldSkipHydratableForInstance (
874
866
instance : HydratableInstance ,
875
867
type : string ,
876
868
props : Props ,
877
- rootOrSingletonContext : boolean ,
878
- ) : null | Instance {
879
- const anyProps = ( props : any ) ;
880
- // We set this first because it must always be set on every invocation
881
- hydratableNode = instance ;
882
- if ( rootOrSingletonContext ) {
883
- // In the head and body we expect 3rd party scripts to
884
- let node ;
885
- for ( ; hydratableNode ; hydratableNode = getNextHydratableSibling ( node ) ) {
886
- node = hydratableNode ;
887
- if ( node . nodeType !== ELEMENT_NODE ) {
888
- // This is a suspense boundary or Text node.
889
- // Suspense Boundaries are never expected to be injected by 3rd parties. If we see one it should be matched
890
- // and this is a hydration error.
891
- // Text Nodes are also not expected to be injected by 3rd parties. This is less of a guarantee for <body>
892
- // but it seems reasonable and conservative to reject this as a hydration error as well
893
- return null ;
894
- } else if (
895
- node . nodeName . toLowerCase ( ) !== type . toLowerCase ( ) ||
896
- isMarkedResource ( node )
897
- ) {
898
- // This is either text or a tag type that differs from the tag we are trying to hydrate
899
- // or a Node we already bound to a hoistable. We skip past it.
900
- continue ;
901
- } else {
902
- // We have an Element with the right type.
903
- const element : Element = ( node : any ) ;
904
-
905
- // We are going to try to exclude it if we can definitely identify it as a hoisted Node or if
906
- // we can guess that the node is likely hoisted or was inserted by a 3rd party script or browser extension
907
- // using high entropy attributes for certain types. This technique will fail for strange insertions like
908
- // extension prepending <div> in the <body> but that already breaks before and that is an edge case.
909
- switch ( type ) {
910
- // case 'title':
911
- //We assume all titles are matchable. You should only have one in the Document, at least in a hoistable scope
912
- // and if you are a HostComponent with type title we must either be in an <svg> context or this title must have an `itemProp` prop.
913
- case 'meta': {
914
- // The only way to opt out of hoisting meta tags is to give it an itemprop attribute. We assume there will be
915
- // not 3rd party meta tags that are prepended, accepting the cases where this isn't true because meta tags
916
- // are usually only functional for SSR so even in a rare case where we did bind to an injected tag the runtime
917
- // implications are minimal
918
- if ( ! element . hasAttribute ( 'itemprop' ) ) {
919
- // This is a Hoistable
920
- continue ;
921
- }
922
- break ;
923
- }
924
- case 'link ': {
925
- // Links come in many forms and we do expect 3rd parties to inject them into <head> / <body>. We exclude known resources
926
- // and then use high-entroy attributes like href which are almost always used and almost always unique to filter out unlikely
927
- // matches.
928
- const rel = element . getAttribute ( 'rel' ) ;
929
- if (
930
- rel === 'stylesheet' &&
931
- element . hasAttribute ( 'data-precedence' )
932
- ) {
933
- // This is a stylesheet resource
934
- continue ;
935
- } else if (
936
- rel !== anyProps . rel ||
937
- element . getAttribute ( 'href' ) !==
938
- ( anyProps . href == null ? null : anyProps . href ) ||
939
- element . getAttribute ( 'crossorigin' ) !==
940
- ( anyProps . crossOrigin == null ? null : anyProps . crossOrigin ) ||
941
- element . getAttribute ( 'title' ) !==
942
- ( anyProps . title == null ? null : anyProps . title )
943
- ) {
944
- // rel + href should usually be enough to uniquely identify a link however crossOrigin can vary for rel preconnect
945
- // and title could vary for rel alternate
946
- continue ;
947
- }
948
- break ;
949
- }
950
- case 'style ': {
951
- // Styles are hard to match correctly. We can exclude known resources but otherwise we accept the fact that a non-hoisted style tags
952
- // in <head> or <body> are likely never going to be unmounted given their position in the document and the fact they likely hold global styles
953
- if ( element . hasAttribute ( 'data-precedence' ) ) {
954
- // This is a style resource
955
- continue ;
956
- }
957
- break ;
958
- }
959
- case 'script ': {
960
- // Scripts are a little tricky, we exclude known resources and then similar to links try to use high-entropy attributes
961
- // to reject poor matches. One challenge with scripts are inline scripts. We don't attempt to check text content which could
962
- // in theory lead to a hydration error later if a 3rd party injected an inline script before the React rendered nodes.
963
- // Falling back to client rendering if this happens should be seemless though so we will try this hueristic and revisit later
964
- // if we learn it is problematic
965
- const srcAttr = element . getAttribute ( 'src' ) ;
966
- if (
967
- srcAttr &&
968
- element . hasAttribute ( 'async' ) &&
969
- ! element . hasAttribute ( 'itemprop' )
970
- ) {
971
- // This is an async script resource
972
- continue ;
973
- } else if (
974
- srcAttr !== ( anyProps . src == null ? null : anyProps . src ) ||
975
- element . getAttribute ( 'type' ) !==
976
- ( anyProps . type == null ? null : anyProps . type ) ||
977
- element . getAttribute ( 'crossorigin' ) !==
978
- ( anyProps . crossOrigin == null ? null : anyProps . crossOrigin )
979
- ) {
980
- // This script is for a different src
981
- continue ;
982
- }
983
- break ;
984
- }
869
+ ) : boolean {
870
+ if ( instance . nodeType !== ELEMENT_NODE ) {
871
+ // This is a suspense boundary or Text node.
872
+ // Suspense Boundaries are never expected to be injected by 3rd parties. If we see one it should be matched
873
+ // and this is a hydration error.
874
+ // Text Nodes are also not expected to be injected by 3rd parties. This is less of a guarantee for <body>
875
+ // but it seems reasonable and conservative to reject this as a hydration error as well
876
+ return false ;
877
+ } else if (
878
+ instance . nodeName . toLowerCase ( ) !== type . toLowerCase ( ) ||
879
+ isMarkedResource ( instance )
880
+ ) {
881
+ // We are either about to
882
+ return true ;
883
+ } else {
884
+ // We have an Element with the right type.
885
+ const element : Element = ( instance : any ) ;
886
+ const anyProps = ( props : any ) ;
887
+
888
+ // We are going to try to exclude it if we can definitely identify it as a hoisted Node or if
889
+ // we can guess that the node is likely hoisted or was inserted by a 3rd party script or browser extension
890
+ // using high entropy attributes for certain types. This technique will fail for strange insertions like
891
+ // extension prepending <div> in the <body> but that already breaks before and that is an edge case.
892
+ switch ( type ) {
893
+ // case 'title':
894
+ //We assume all titles are matchable. You should only have one in the Document, at least in a hoistable scope
895
+ // and if you are a HostComponent with type title we must either be in an <svg> context or this title must have an `itemProp` prop.
896
+ case 'meta' : {
897
+ // The only way to opt out of hoisting meta tags is to give it an itemprop attribute. We assume there will be
898
+ // not 3rd party meta tags that are prepended, accepting the cases where this isn't true because meta tags
899
+ // are usually only functional for SSR so even in a rare case where we did bind to an injected tag the runtime
900
+ // implications are minimal
901
+ if ( ! element . hasAttribute ( 'itemprop' ) ) {
902
+ // This is a Hoistable
903
+ return true ;
904
+ }
905
+ break ;
906
+ }
907
+ case 'link' : {
908
+ // Links come in many forms and we do expect 3rd parties to inject them into <head> / <body>. We exclude known resources
909
+ // and then use high-entroy attributes like href which are almost always used and almost always unique to filter out unlikely
910
+ // matches.
911
+ const rel = element . getAttribute ( 'rel' ) ;
912
+ if ( rel === 'stylesheet' && element . hasAttribute ( 'data-precedence' ) ) {
913
+ // This is a stylesheet resource
914
+ return true ;
915
+ } else if (
916
+ rel !== anyProps . rel ||
917
+ element . getAttribute ( 'href' ) !==
918
+ ( anyProps . href == null ? null : anyProps . href ) ||
919
+ element . getAttribute ( 'crossorigin' ) !==
920
+ ( anyProps . crossOrigin == null ? null : anyProps . crossOrigin ) ||
921
+ element . getAttribute ( 'title' ) !==
922
+ ( anyProps . title == null ? null : anyProps . title )
923
+ ) {
924
+ // rel + href should usually be enough to uniquely identify a link however crossOrigin can vary for rel preconnect
925
+ // and title could vary for rel alternate
926
+ return true ;
927
+ }
928
+ break ;
929
+ }
930
+ case 'style' : {
931
+ // Styles are hard to match correctly. We can exclude known resources but otherwise we accept the fact that a non-hoisted style tags
932
+ // in <head> or <body> are likely never going to be unmounted given their position in the document and the fact they likely hold global styles
933
+ if ( element . hasAttribute ( 'data-precedence' ) ) {
934
+ // This is a style resource
935
+ return true ;
936
+ }
937
+ break ;
938
+ }
939
+ case 'script' : {
940
+ // Scripts are a little tricky, we exclude known resources and then similar to links try to use high-entropy attributes
941
+ // to reject poor matches. One challenge with scripts are inline scripts. We don't attempt to check text content which could
942
+ // in theory lead to a hydration error later if a 3rd party injected an inline script before the React rendered nodes.
943
+ // Falling back to client rendering if this happens should be seemless though so we will try this hueristic and revisit later
944
+ // if we learn it is problematic
945
+ const srcAttr = element . getAttribute ( 'src' ) ;
946
+ if (
947
+ srcAttr &&
948
+ element . hasAttribute ( 'async' ) &&
949
+ ! element . hasAttribute ( 'itemprop' )
950
+ ) {
951
+ // This is an async script resource
952
+ return true ;
953
+ } else if (
954
+ srcAttr !== ( anyProps . src == null ? null : anyProps . src ) ||
955
+ element . getAttribute ( 'type' ) !==
956
+ ( anyProps . type == null ? null : anyProps . type ) ||
957
+ element . getAttribute ( 'crossorigin' ) !==
958
+ ( anyProps . crossOrigin == null ? null : anyProps . crossOrigin )
959
+ ) {
960
+ // This script is for a different src
961
+ return true ;
985
962
}
986
- // We have excluded the most likely cases of mismatch between hoistable tags, 3rd party script inserted tags,
987
- // and browser extension inserted tags. While it is possible this is not the right match it is a decent hueristic
988
- // that should work in the vast majority of cases.
989
- return element ;
963
+ break ;
990
964
}
991
965
}
966
+ // We have excluded the most likely cases of mismatch between hoistable tags, 3rd party script inserted tags,
967
+ // and browser extension inserted tags. While it is possible this is not the right match it is a decent hueristic
968
+ // that should work in the vast majority of cases.
969
+ return false ;
970
+ }
971
+ }
972
+
973
+ export function shouldSkipHydratableForTextInstance (
974
+ instance : HydratableInstance ,
975
+ ) : boolean {
976
+ return instance . nodeType === ELEMENT_NODE ;
977
+ }
978
+
979
+ export function shouldSkipHydratableForSuspenseInstance (
980
+ instance : HydratableInstance ,
981
+ ) : boolean {
982
+ return instance . nodeType === ELEMENT_NODE ;
983
+ }
984
+
985
+ export function canHydrateInstance (
986
+ instance : HydratableInstance ,
987
+ type : string ,
988
+ props : Props ,
989
+ ) : null | Instance {
990
+ if (
991
+ instance . nodeType !== ELEMENT_NODE ||
992
+ instance . nodeName . toLowerCase ( ) !== type . toLowerCase ( )
993
+ ) {
992
994
return null ;
993
995
} else {
994
- if (
995
- instance . nodeType !== ELEMENT_NODE ||
996
- instance . nodeName . toLowerCase ( ) !== type . toLowerCase ( )
997
- ) {
998
- return null ;
999
- } else {
1000
- return ( ( instance : any ) : Instance ) ;
1001
- }
996
+ return ( ( instance : any ) : Instance ) ;
1002
997
}
1003
998
}
1004
999
1005
- export function getNextMatchingHydratableTextInstance (
1000
+ export function canHydrateTextInstance (
1006
1001
instance : HydratableInstance ,
1007
1002
text : string ,
1008
- rootOrSingletonContext : boolean ,
1009
1003
) : null | TextInstance {
1010
- // We set this first because it must always be set on every invocation
1011
- hydratableNode = instance ;
1012
-
1013
- // Return early if there is nothing to hydrate (there will be no dom node if empty text)
1014
1004
if ( text === '' ) return null ;
1015
1005
1016
- if ( rootOrSingletonContext ) {
1017
- while ( hydratableNode ) {
1018
- const node = hydratableNode ;
1019
- if ( node . nodeType === COMMENT_NODE ) {
1020
- // This is a suspense boundary we must halt here because we know this was not injected by 3rd party
1021
- return null ;
1022
- } else if ( node . nodeType !== TEXT_NODE ) {
1023
- // Empty strings are not parsed by HTML so there won't be a correct match here.
1024
- hydratableNode = getNextHydratableSibling ( node ) ;
1025
- continue ;
1026
- }
1027
- // This has now been refined to a text node.
1028
- return ( ( hydratableNode : any ) : TextInstance ) ;
1029
- }
1030
- } else {
1031
- if ( instance . nodeType !== TEXT_NODE ) {
1032
- // Empty strings are not parsed by HTML so there won't be a correct match here.
1033
- return null ;
1034
- }
1035
- // This has now been refined to a text node.
1036
- return ( ( instance : any ) : TextInstance ) ;
1006
+ if ( instance . nodeType !== TEXT_NODE ) {
1007
+ // Empty strings are not parsed by HTML so there won't be a correct match here.
1008
+ return null ;
1037
1009
}
1038
-
1039
- return null ;
1010
+ // This has now been refined to a text node.
1011
+ return ( ( instance : any ) : TextInstance ) ;
1040
1012
}
1041
1013
1042
- export function getNextMatchingHydratableSuspenseInstance (
1014
+ export function canHydrateSuspenseInstance (
1043
1015
instance : HydratableInstance ,
1044
- rootOrSingletonContext : boolean ,
1045
1016
) : null | SuspenseInstance {
1046
- // We set this first because it must always be set on every invocation
1047
- hydratableNode = instance ;
1048
-
1049
- if ( rootOrSingletonContext ) {
1050
- while ( hydratableNode ) {
1051
- if ( hydratableNode . nodeType !== COMMENT_NODE ) {
1052
- hydratableNode = getNextHydratableSibling ( hydratableNode ) ;
1053
- continue ;
1054
- }
1055
- // This has now been refined to a suspense node.
1056
- return ( ( hydratableNode : any ) : SuspenseInstance ) ;
1057
- }
1017
+ if ( instance . nodeType !== COMMENT_NODE ) {
1058
1018
return null ;
1059
- } else {
1060
- if ( instance . nodeType !== COMMENT_NODE ) {
1061
- return null ;
1062
- }
1063
- // This has now been refined to a suspense node.
1064
- return ( ( instance : any ) : SuspenseInstance ) ;
1065
1019
}
1020
+ // This has now been refined to a suspense node.
1021
+ return ( ( instance : any ) : SuspenseInstance ) ;
1066
1022
}
1067
1023
1068
1024
export function isSuspenseInstancePending ( instance : SuspenseInstance ) : boolean {
0 commit comments