@@ -4451,12 +4451,6 @@ function getStyleKey(href: string) {
44514451 return `href="${ limitedEscapedHref } "` ;
44524452}
44534453
4454- function getStyleTagSelector ( href : string ) {
4455- const limitedEscapedHref =
4456- escapeSelectorAttributeValueInsideDoubleQuotes ( href ) ;
4457- return `style[data-href~="${ limitedEscapedHref } "]` ;
4458- }
4459-
44604454function getStylesheetSelectorFromKey ( key : string ) {
44614455 return `link[rel="stylesheet"][${ key } ]` ;
44624456}
@@ -4522,6 +4516,64 @@ function getScriptSelectorFromKey(key: string): string {
45224516 return 'script[async]' + key ;
45234517}
45244518
4519+ // for the sake of performance we want to use insertRule
4520+ // if that fails, fall back to appending textContent (which is slow)
4521+ function appendStyleRule ( style : HTMLStyleElement , cssText : string ) {
4522+ try {
4523+ if ( style . sheet )
4524+ style . sheet . insertRule ( cssText , style . sheet . cssRules . length ) ;
4525+ else style . textContent += cssText ;
4526+ } catch ( e ) {
4527+ style . textContent += cssText ;
4528+ }
4529+ }
4530+
4531+ const styleNodesByPrecedence = new WeakMap <
4532+ HoistableRoot ,
4533+ Map < string , HTMLStyleElement > ,
4534+ > ( ) ;
4535+ const styleNodesByHref = new WeakMap < HoistableRoot , Map < string , Instance >> ( ) ;
4536+
4537+ // when creating our caches, hydrate with data from the DOM
4538+ // this should only happen once per root
4539+ function getHydratedCaches ( hoistableRoot : HoistableRoot ) {
4540+ let rootHrefCache = styleNodesByHref . get ( hoistableRoot ) ;
4541+ let rootPrecedenceCache = styleNodesByPrecedence . get ( hoistableRoot ) ;
4542+
4543+ if ( ! rootHrefCache ) {
4544+ rootHrefCache = new Map < string , Instance > ( ) ;
4545+ styleNodesByHref . set ( hoistableRoot , rootHrefCache ) ;
4546+
4547+ const nodesWithHref = hoistableRoot . querySelectorAll ( 'style[data-href]' ) ;
4548+ for ( let i = 0 ; i < nodesWithHref . length ; i ++ ) {
4549+ // populate our cache for all hrefs listed in this node's data-href
4550+ const node = nodesWithHref [ i ] ;
4551+ const href = node . dataset . href ;
4552+ for ( let j = 0 ; j < href . length ; j ++ ) {
4553+ const h = href [ j ] ;
4554+ if ( node instanceof HTMLStyleElement ) rootHrefCache . set ( h , node ) ;
4555+ }
4556+ }
4557+ }
4558+
4559+ if ( ! rootPrecedenceCache ) {
4560+ rootPrecedenceCache = new Map < string , HTMLStyleElement > ( ) ;
4561+ styleNodesByPrecedence . set ( hoistableRoot , rootPrecedenceCache ) ;
4562+
4563+ const nodesWithPrecedence = hoistableRoot . querySelectorAll (
4564+ 'style[data-precedence]' ,
4565+ ) ;
4566+ for ( let i = 0 ; i < nodesWithPrecedence . length ; i ++ ) {
4567+ const node = nodesWithPrecedence [ i ] ;
4568+ const precedence = node . dataset . precedence ;
4569+ if ( node instanceof HTMLStyleElement )
4570+ rootPrecedenceCache . set ( precedence , node ) ;
4571+ }
4572+ }
4573+
4574+ return { rootHrefCache, rootPrecedenceCache} ;
4575+ }
4576+
45254577export function acquireResource (
45264578 hoistableRoot : HoistableRoot ,
45274579 resource : Resource ,
@@ -4533,16 +4585,31 @@ export function acquireResource(
45334585 case 'style ': {
45344586 const qualifiedProps : StyleTagQualifyingProps = props ;
45354587
4536- // Attempt to hydrate instance from DOM
4537- let instance : null | Instance = hoistableRoot . querySelector (
4538- getStyleTagSelector ( qualifiedProps . href ) ,
4539- ) ;
4588+ const { rootHrefCache , rootPrecedenceCache } =
4589+ getHydratedCaches ( hoistableRoot ) ;
4590+
4591+ // attempt to hydrate first from our cache, then from the DOM
4592+ // this minimizes the number of times we query the DOM
4593+ let instance : void | Instance = rootHrefCache . get ( qualifiedProps . href ) ;
4594+
45404595 if ( instance ) {
45414596 resource . instance = instance ;
45424597 markNodeAsHoistable ( instance ) ;
45434598 return instance ;
45444599 }
45454600
4601+ // attempt to reuse an existing style node before creating a new one
4602+ // this minimizes the number of style nodes we create, which keeps query selectors faster
4603+ instance = rootPrecedenceCache . get ( qualifiedProps . precedence ) ;
4604+ if ( instance && instance . isConnected ) {
4605+ if ( typeof qualifiedProps . children === 'string' )
4606+ appendStyleRule ( instance , qualifiedProps . children ) ;
4607+ if ( ! instance . dataset . href . includes ( qualifiedProps . href ) )
4608+ instance . dataset . href += ` ${ qualifiedProps . href } ` ;
4609+ resource . instance = instance ;
4610+ return instance ;
4611+ }
4612+
45464613 const styleProps = styleTagPropsFromRawProps ( props ) ;
45474614 const ownerDocument = getDocumentFromRoot ( hoistableRoot ) ;
45484615 instance = ownerDocument . createElement ( 'style' ) ;
@@ -4556,6 +4623,7 @@ export function acquireResource(
45564623 // resource.state.loading |= Inserted;
45574624 insertStylesheet ( instance , qualifiedProps . precedence , hoistableRoot ) ;
45584625 resource . instance = instance ;
4626+ rootPrecedenceCache . set ( qualifiedProps . precedence , instance ) ;
45594627
45604628 return instance ;
45614629 }
0 commit comments