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,11 +7,74 @@ import {
77} from "../shared_internal.tsx" ;
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.tsx" ;
1112
1213// deno-lint-ignore no-explicit-any
1314const options : InternalPreactOptions = preactOptions as any ;
1415
16+ const PATCHED = new WeakSet < VNode > ( ) ;
17+
18+ function WrappedHead (
19+ // deno-lint-ignore no-explicit-any
20+ { originalType, props, key } : { originalType : string ; props : any ; key : any } ,
21+ ) {
22+ const enabled = useContext ( HeadContext ) ;
23+
24+ useEffect ( ( ) => {
25+ if ( ! enabled ) return ;
26+
27+ const text = renderToString ( h ( Fragment , null , props . children ) ) ;
28+
29+ if ( originalType === "title" ) {
30+ document . title = text ;
31+ return ;
32+ }
33+
34+ let matched : HTMLElement | null = null ;
35+ if ( key ) {
36+ matched = document . head . querySelector (
37+ `head [data-key="${ key } "]` ,
38+ ) as HTMLElement ?? null ;
39+ }
40+
41+ if ( matched === null && props . id ) {
42+ matched = document . head . querySelector (
43+ `#${ props . name } ` ,
44+ ) as HTMLElement ??
45+ null ;
46+ }
47+
48+ if ( matched === null ) {
49+ if ( originalType === "meta" ) {
50+ matched = document . head . querySelector (
51+ `head [name="${ props . name } "]` ,
52+ ) as HTMLElement ?? null ;
53+ } else if ( originalType === "base" ) {
54+ matched = document . head . querySelector ( originalType ) ?? null ;
55+ }
56+ }
57+
58+ if ( matched === null ) {
59+ matched = document . createElement ( originalType ) ;
60+ }
61+
62+ if ( matched . textContent !== text ) {
63+ matched . textContent = text ;
64+ }
65+
66+ applyProps ( props , matched ) ;
67+ } , [ ] ) ;
68+
69+ if ( enabled ) {
70+ return null ;
71+ }
72+
73+ const inner = h ( originalType , props ) ;
74+ PATCHED . add ( inner ) ;
75+ return inner ;
76+ }
77+
1578const oldVNodeHook = options . vnode ;
1679options . vnode = ( vnode ) => {
1780 assetHashingHook ( vnode , BUILD_ID ) ;
@@ -28,63 +91,24 @@ options.vnode = (vnode) => {
2891 const originalType = vnode . type ;
2992
3093 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-explicit-any
41- vnode . type = ( props : any ) => {
42- useEffect ( ( ) => {
43- const text = renderToString ( h ( Fragment , null , props . children ) ) ;
44-
45- if ( originalType === "title" ) {
46- document . title = text ;
47- return ;
48- }
49-
50- let matched : HTMLElement | null = null ;
51- if ( vnode . key ) {
52- matched = document . head . querySelector (
53- `head [data-key="${ vnode . key } "]` ,
54- ) as HTMLElement ?? null ;
55- }
56-
57- if ( matched === null && props . id ) {
58- matched = document . head . querySelector (
59- `#${ props . name } ` ,
60- ) as HTMLElement ??
61- null ;
62- }
63-
64- if ( matched === null ) {
65- if ( originalType === "meta" ) {
66- matched = document . head . querySelector (
67- `head [name="${ props . name } "]` ,
68- ) as HTMLElement ?? null ;
69- } else if ( originalType === "base" ) {
70- matched = document . head . querySelector ( originalType ) ?? null ;
71- }
72- }
73-
74- if ( matched === null ) {
75- matched = document . createElement ( originalType ) ;
76- }
77-
78- if ( matched . textContent !== text ) {
79- matched . textContent = text ;
80- }
81-
82- applyProps ( props , matched ) ;
83- } , [ ] ) ;
84-
85- return null ;
86- } ;
87- break ;
94+ if ( ! PATCHED . has ( vnode ) ) {
95+ switch ( originalType ) {
96+ case "title" :
97+ case "meta" :
98+ case "link" :
99+ case "script" :
100+ case "style" :
101+ case "base" :
102+ case "noscript" :
103+ case "template" :
104+ vnode = h ( WrappedHead , {
105+ originalType,
106+ props : vnode . props ,
107+ key : vnode . key ,
108+ // deno-lint-ignore no-explicit-any
109+ } ) as any ;
110+ break ;
111+ }
88112 }
89113 }
90114
0 commit comments