1- import { Fragment , h , options as preactOptions } from "preact" ;
1+ import { Fragment , h , options as preactOptions , type VNode } from "preact" ;
22import {
33 assetHashingHook ,
44 CLIENT_NAV_ATTR ,
@@ -7,87 +7,109 @@ import {
77} from "../shared_internal.ts" ;
88import { BUILD_ID } from "@fresh/build-id" ;
99import { renderToString } from "preact-render-to-string" ;
10- import { useEffect } from "preact/hooks" ;
10+ import { useContext , useEffect } from "preact/hooks" ;
11+ import { HeadContext } from "../head.ts" ;
1112
1213// deno-lint-ignore no-explicit-any
1314const options : InternalPreactOptions = preactOptions as any ;
1415
16+ function WrappedHead (
17+ // deno-lint-ignore no-explicit-any
18+ { originalType, props, key } : { originalType : string ; props : any ; key : any } ,
19+ ) {
20+ const enabled = useContext ( HeadContext ) ;
21+
22+ useEffect ( ( ) => {
23+ if ( ! enabled ) return ;
24+
25+ const text = renderToString ( h ( Fragment , null , props . children ) ) ;
26+
27+ if ( originalType === "title" ) {
28+ document . title = text ;
29+ return ;
30+ }
31+
32+ let matched : HTMLElement | null = null ;
33+ if ( key ) {
34+ matched = document . head . querySelector (
35+ `head [data-key="${ key } "]` ,
36+ ) as HTMLElement ?? null ;
37+ }
38+
39+ if ( matched === null && props . id ) {
40+ matched = document . head . querySelector (
41+ `#${ props . id } ` ,
42+ ) as HTMLElement ??
43+ null ;
44+ }
45+
46+ if ( matched === null ) {
47+ if ( originalType === "meta" ) {
48+ matched = document . head . querySelector (
49+ `head [name="${ props . name } "]` ,
50+ ) as HTMLElement ?? null ;
51+ } else if ( originalType === "link" && props . rel ) {
52+ matched = document . head . querySelector (
53+ `head link[rel="${ props . rel } "]` ,
54+ ) as HTMLElement ?? null ;
55+ } else if ( originalType === "base" ) {
56+ matched = document . head . querySelector ( originalType ) ?? null ;
57+ }
58+ }
59+
60+ if ( matched === null ) {
61+ matched = document . createElement ( originalType ) ;
62+ }
63+
64+ if ( matched . textContent !== text ) {
65+ matched . textContent = text ;
66+ }
67+
68+ applyProps ( props , matched ) ;
69+ } , [ originalType , props , key ] ) ;
70+
71+ if ( enabled ) {
72+ return null ;
73+ }
74+
75+ return h ( originalType , { ...props , _freshPatched : true } ) ;
76+ }
77+
1578const oldVNodeHook = options . vnode ;
1679options . vnode = ( vnode ) => {
1780 assetHashingHook ( vnode , BUILD_ID ) ;
1881
19- if ( typeof vnode . type === "string" ) {
82+ const originalType = vnode . type ;
83+ if ( typeof originalType === "string" ) {
2084 if ( CLIENT_NAV_ATTR in vnode . props ) {
2185 const value = vnode . props [ CLIENT_NAV_ATTR ] ;
2286 if ( typeof value === "boolean" ) {
2387 vnode . props [ CLIENT_NAV_ATTR ] = String ( value ) ;
2488 }
25- }
26- }
27-
28- const originalType = vnode . type ;
29-
30- if ( typeof originalType === "string" ) {
31- switch ( originalType ) {
32- case "title" :
33- case "meta" :
34- case "link" :
35- case "script" :
36- case "style" :
37- case "base" :
38- case "noscript" :
39- case "template" :
40- // deno-lint-ignore no-constant-condition
41- if ( false ) {
89+ // deno-lint-ignore no-explicit-any
90+ } else if ( ! ( vnode . props as any ) . _freshPatched ) {
91+ switch ( originalType ) {
92+ case "title" :
93+ case "meta" :
94+ case "link" :
95+ case "script" :
96+ case "style" :
97+ case "base" :
98+ case "noscript" :
99+ case "template" : {
42100 // deno-lint-ignore no-explicit-any
43- vnode . type = ( props : any ) => {
44- useEffect ( ( ) => {
45- const text = renderToString ( h ( Fragment , null , props . children ) ) ;
46-
47- if ( originalType === "title" ) {
48- document . title = text ;
49- return ;
50- }
51-
52- let matched : HTMLElement | null = null ;
53- if ( vnode . key ) {
54- matched = document . head . querySelector (
55- `head [data-key="${ vnode . key } "]` ,
56- ) as HTMLElement ?? null ;
57- }
58-
59- if ( matched === null && props . id ) {
60- matched = document . head . querySelector (
61- `#${ props . name } ` ,
62- ) as HTMLElement ??
63- null ;
64- }
65-
66- if ( matched === null ) {
67- if ( originalType === "meta" ) {
68- matched = document . head . querySelector (
69- `head [name="${ props . name } "]` ,
70- ) as HTMLElement ?? null ;
71- } else if ( originalType === "base" ) {
72- matched = document . head . querySelector ( originalType ) ?? null ;
73- }
74- }
75-
76- if ( matched === null ) {
77- matched = document . createElement ( originalType as string ) ;
78- }
79-
80- if ( matched . textContent !== text ) {
81- matched . textContent = text ;
82- }
83-
84- applyProps ( props , matched ) ;
85- } , [ ] ) ;
86-
87- return null ;
101+ const v = vnode as VNode < any > ;
102+ const props = vnode . props ;
103+ const key = vnode . key ;
104+ v . type = WrappedHead ;
105+ v . props = {
106+ originalType,
107+ props,
108+ key,
88109 } ;
110+ break ;
89111 }
90- break ;
112+ }
91113 }
92114 }
93115
0 commit comments