1
1
import type {
2
+ ActiveHeadEntry ,
2
3
CreateHeadOptions ,
3
4
Head ,
4
5
HeadEntry ,
@@ -18,15 +19,13 @@ function filterMode(mode: RuntimeMode | undefined, ssr: boolean) {
18
19
}
19
20
20
21
function registerPlugins ( head : Unhead < any > , plugins : HeadPluginInput [ ] , ssr : boolean ) {
21
- plugins . forEach ( ( p ) => {
22
+ for ( const p of plugins ) {
22
23
const plugin = ( typeof p === 'function' ? p ( head ) : p )
23
- if ( ! plugin . key || ! head . plugins . some ( existingPlugin => existingPlugin . key === plugin . key ) ) {
24
- head . plugins . push ( plugin )
25
- if ( filterMode ( plugin . mode , ssr ) ) {
26
- head . hooks . addHooks ( plugin . hooks || { } )
27
- }
24
+ if ( filterMode ( plugin . mode , ssr ) && ! head . plugins . has ( plugin . key ) ) {
25
+ head . plugins . set ( plugin . key , plugin )
26
+ head . hooks . addHooks ( plugin . hooks || { } )
28
27
}
29
- } )
28
+ }
30
29
}
31
30
32
31
/**
@@ -40,60 +39,54 @@ export function createHeadCore<T extends Record<string, any> = Head>(options: Cr
40
39
const hooks = createHooks < HeadHooks > ( )
41
40
hooks . addHooks ( options . hooks || { } )
42
41
const ssr = ! options . document
43
-
44
- const updated = ( ) => {
45
- // eslint-disable-next-line ts/no-use-before-define
46
- head . dirty = true
47
- // eslint-disable-next-line ts/no-use-before-define
48
- hooks . callHook ( 'entries:updated' , head )
49
- }
50
- let entryCount = 0
51
- let entries : HeadEntry < T > [ ] = [ ]
52
- const plugins : HeadPlugin [ ] = [ ]
42
+ const entries : Map < number , HeadEntry < T > > = new Map ( )
43
+ const plugins : Map < string , HeadPlugin > = new Map ( )
53
44
const head : Unhead < T > = {
45
+ _entryCount : 1 , // 0 is reserved for internal use
54
46
plugins,
55
47
dirty : false ,
56
48
resolvedOptions : options ,
57
49
hooks,
58
50
ssr,
51
+ entries,
59
52
headEntries ( ) {
60
- return entries
53
+ return [ ... entries . values ( ) ]
61
54
} ,
62
55
use ( p : HeadPluginInput ) {
63
56
registerPlugins ( head , [ p ] , ssr )
64
57
} ,
65
- push ( input , entryOptions ) {
66
- delete entryOptions ?. head
67
- const entry : HeadEntry < T > = {
68
- _i : entryCount ++ ,
69
- input,
70
- ...entryOptions as Partial < HeadEntry < T > > ,
71
- }
72
- // bit hacky but safer
73
- if ( filterMode ( entry . mode , ssr ) ) {
74
- entries . push ( entry )
75
- updated ( )
76
- }
77
- return {
58
+ push ( input , _options ) {
59
+ const options = { ..._options } as Partial < HeadEntry < T > >
60
+ // @ts -expect-error untyped
61
+ delete options . head
62
+ const _i = head . _entryCount ++
63
+ const _ : ActiveHeadEntry < T > = {
64
+ _poll ( ) {
65
+ head . dirty = true
66
+ hooks . callHook ( 'entries:updated' , head )
67
+ } ,
78
68
dispose ( ) {
79
- entries = entries . filter ( e => e . _i !== entry . _i )
80
- updated ( )
69
+ if ( entries . delete ( _i ) ) {
70
+ _ . _poll ( )
71
+ }
81
72
} ,
82
73
// a patch is the same as creating a new entry, just a nice DX
83
74
patch ( input ) {
84
- for ( const e of entries ) {
85
- if ( e . _i === entry . _i ) {
86
- // bit hacky syncing
87
- e . input = entry . input = input
88
- e . resolvedInput = undefined
89
- }
75
+ if ( filterMode ( options . mode , ssr ) ) {
76
+ entries . set ( _i , {
77
+ _i,
78
+ input,
79
+ ...options ,
80
+ } )
81
+ _ . _poll ( )
90
82
}
91
- updated ( )
92
83
} ,
93
84
}
85
+ _ . patch ( input )
86
+ return _
94
87
} ,
95
88
async resolveTags ( ) {
96
- const resolveCtx : { tags : HeadTag [ ] , entries : HeadEntry < T > [ ] } = { tags : [ ] , entries : [ ...entries ] }
89
+ const resolveCtx : { tags : HeadTag [ ] , entries : HeadEntry < T > [ ] } = { tags : [ ] , entries : [ ...head . entries . values ( ) ] }
97
90
await hooks . callHook ( 'entries:resolve' , resolveCtx )
98
91
for ( const entry of resolveCtx . entries ) {
99
92
// apply any custom transformers applied to the entry
0 commit comments