unhead 0.0.6 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +20 -15
- package/dist/index.mjs +269 -134
- package/package.json +7 -12
- package/dist/client.d.ts +0 -24
- package/dist/client.mjs +0 -89
- package/dist/server.d.ts +0 -3
- package/dist/server.mjs +0 -13
- package/dist/types-b4318c02.d.ts +0 -131
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import * as _unhead_schema from '@unhead/schema';
|
|
2
|
-
import { Head, Meta, Link, Script, Style, Base, HtmlAttributes, BodyAttributes, Noscript } from '@unhead/schema';
|
|
3
|
-
import { H as HeadPlugin, C as CreateHeadOptions, a as HeadClient, b as HeadEntryOptions } from './types-b4318c02.js';
|
|
4
|
-
export { A as ActiveHeadEntry, C as CreateHeadOptions, D as DomRenderTagContext, E as EntryResolveCtx, a as HeadClient, c as HeadEntry, b as HeadEntryOptions, f as HeadHooks, H as HeadPlugin, e as HookResult, R as RuntimeMode, S as SideEffectsRecord, d as defineHeadPlugin } from './types-b4318c02.js';
|
|
5
|
-
import 'hookable';
|
|
2
|
+
import { Head, HeadEntryOptions, Meta, Link, Script, Style, Base, HtmlAttributes, BodyAttributes, Noscript, HeadClient, CreateHeadOptions, HeadPlugin, HeadTag, HeadEntry } from '@unhead/schema';
|
|
6
3
|
|
|
7
|
-
|
|
4
|
+
interface DedupesTagsPluginOptions {
|
|
5
|
+
dedupeKeys?: string[];
|
|
6
|
+
}
|
|
7
|
+
declare const DedupesTagsPlugin: (options?: DedupesTagsPluginOptions) => _unhead_schema.HeadPlugin;
|
|
8
8
|
|
|
9
|
-
declare const SortTagsPlugin:
|
|
9
|
+
declare const SortTagsPlugin: () => _unhead_schema.HeadPlugin;
|
|
10
10
|
|
|
11
|
-
declare const TitleTemplatePlugin:
|
|
11
|
+
declare const TitleTemplatePlugin: () => _unhead_schema.HeadPlugin;
|
|
12
12
|
|
|
13
|
-
declare const HydratesStatePlugin:
|
|
13
|
+
declare const HydratesStatePlugin: () => _unhead_schema.HeadPlugin;
|
|
14
14
|
|
|
15
|
-
declare function
|
|
16
|
-
|
|
17
|
-
declare function useHead<T>(input: T, options?: HeadEntryOptions): void;
|
|
15
|
+
declare function useHead<T extends Head>(input: T, options?: HeadEntryOptions): void;
|
|
16
|
+
declare function useServerHead<T extends Head>(input: T, options?: HeadEntryOptions): void;
|
|
18
17
|
declare const useTitle: (title: string) => void;
|
|
19
18
|
declare const useMeta: (meta: Meta) => void;
|
|
20
19
|
declare const useLink: (link: Link) => void;
|
|
@@ -27,12 +26,18 @@ declare const useTitleTemplate: (titleTemplate: string) => void;
|
|
|
27
26
|
declare const useNoscript: (noscript: Noscript) => void;
|
|
28
27
|
|
|
29
28
|
declare let activeHead: HeadClient<any> | undefined;
|
|
30
|
-
declare const setActiveHead: <T
|
|
31
|
-
declare const getActiveHead: <T
|
|
29
|
+
declare const setActiveHead: <T extends HeadClient<_unhead_schema.Head<_unhead_schema.SchemaAugmentations>>>(head: T | undefined) => T | undefined;
|
|
30
|
+
declare const getActiveHead: <T extends HeadClient<_unhead_schema.Head<_unhead_schema.SchemaAugmentations>>>() => T;
|
|
31
|
+
|
|
32
|
+
declare function createHead<T extends {} = Head>(options?: CreateHeadOptions): HeadClient<T>;
|
|
33
|
+
|
|
34
|
+
declare function defineHeadPlugin(plugin: HeadPlugin): HeadPlugin;
|
|
35
|
+
|
|
36
|
+
declare function normaliseTag<T>(tagName: HeadTag['tag'], input: HeadTag['props'], entry: HeadEntry<T>): HeadTag | HeadTag[];
|
|
37
|
+
declare function normaliseEntryTags<T extends {} = Head>(e: HeadEntry<T>): HeadTag[];
|
|
32
38
|
|
|
33
39
|
declare type Arrayable<T> = T | Array<T>;
|
|
34
40
|
declare function asArray<T>(value: Arrayable<T>): T[];
|
|
35
41
|
declare const TagConfigKeys: string[];
|
|
36
|
-
declare function hashCode(s: string): string;
|
|
37
42
|
|
|
38
|
-
export { Arrayable, DedupesTagsPlugin, HydratesStatePlugin, SortTagsPlugin, TagConfigKeys, TitleTemplatePlugin, activeHead, asArray, createHead, getActiveHead,
|
|
43
|
+
export { Arrayable, DedupesTagsPlugin, DedupesTagsPluginOptions, HydratesStatePlugin, SortTagsPlugin, TagConfigKeys, TitleTemplatePlugin, activeHead, asArray, createHead, defineHeadPlugin, getActiveHead, normaliseEntryTags, normaliseTag, setActiveHead, useBase, useBodyAttrs, useHead, useHtmlAttrs, useLink, useMeta, useNoscript, useScript, useServerHead, useStyle, useTitle, useTitleTemplate };
|
package/dist/index.mjs
CHANGED
|
@@ -1,103 +1,216 @@
|
|
|
1
1
|
import { createHooks } from 'hookable';
|
|
2
|
-
import { tagDedupeKey, sortCriticalTags, resolveTitleTemplateFromTags, HasElementTags, ValidHeadTags, normaliseTag as normaliseTag$1 } from 'zhead';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
3
|
+
function normaliseTag$1(tagName, input, options = {}) {
|
|
4
|
+
const tag = { tag: tagName, props: {} };
|
|
5
|
+
if (tagName === "title")
|
|
6
|
+
tag.children = String(input);
|
|
7
|
+
else
|
|
8
|
+
tag.props = normaliseProps({ ...input });
|
|
9
|
+
["children", ...options?.childrenKeys || []].forEach((key) => {
|
|
10
|
+
if (typeof tag.props[key] !== "undefined") {
|
|
11
|
+
tag.children = tag.props[key];
|
|
12
|
+
delete tag.props[key];
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return tag;
|
|
16
|
+
}
|
|
17
|
+
function normaliseProps(props) {
|
|
18
|
+
for (const k in props) {
|
|
19
|
+
if (String(props[k]) === "true") {
|
|
20
|
+
props[k] = "";
|
|
21
|
+
} else if (String(props[k]) === "false") {
|
|
22
|
+
delete props[k];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return props;
|
|
26
|
+
}
|
|
27
|
+
const HasElementTags = [
|
|
28
|
+
"base",
|
|
29
|
+
"meta",
|
|
30
|
+
"link",
|
|
31
|
+
"style",
|
|
32
|
+
"script",
|
|
33
|
+
"noscript"
|
|
34
|
+
];
|
|
35
|
+
const ValidHeadTags = [
|
|
36
|
+
"title",
|
|
37
|
+
"titleTemplate",
|
|
38
|
+
"base",
|
|
39
|
+
"htmlAttrs",
|
|
40
|
+
"bodyAttrs",
|
|
41
|
+
"meta",
|
|
42
|
+
"link",
|
|
43
|
+
"style",
|
|
44
|
+
"script",
|
|
45
|
+
"noscript"
|
|
46
|
+
];
|
|
7
47
|
|
|
8
|
-
|
|
9
|
-
|
|
48
|
+
const sortCriticalTags = (aTag, bTag) => {
|
|
49
|
+
const tagWeight = (tag) => {
|
|
50
|
+
switch (tag.tag) {
|
|
51
|
+
case "base":
|
|
52
|
+
return -1;
|
|
53
|
+
case "title":
|
|
54
|
+
return 1;
|
|
55
|
+
case "meta":
|
|
56
|
+
if (tag.props.charset)
|
|
57
|
+
return -2;
|
|
58
|
+
if (tag.props["http-equiv"] === "content-security-policy")
|
|
59
|
+
return 0;
|
|
60
|
+
return 10;
|
|
61
|
+
default:
|
|
62
|
+
return 10;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return tagWeight(aTag) - tagWeight(bTag);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function tagDedupeKey(tag) {
|
|
69
|
+
const { props, tag: tagName } = tag;
|
|
70
|
+
if (["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs"].includes(tagName))
|
|
71
|
+
return tagName;
|
|
72
|
+
if (tagName === "link" && props.rel === "canonical")
|
|
73
|
+
return "canonical";
|
|
74
|
+
if (props.charset)
|
|
75
|
+
return "charset";
|
|
76
|
+
const name = ["id"];
|
|
77
|
+
if (tagName === "meta")
|
|
78
|
+
name.push(...["name", "property", "http-equiv"]);
|
|
79
|
+
for (const n of name) {
|
|
80
|
+
if (typeof props[n] !== "undefined") {
|
|
81
|
+
return `${tagName}:${n}:${props[n]}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
10
85
|
}
|
|
11
86
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
87
|
+
const renderTitleTemplate = (template, title) => {
|
|
88
|
+
if (template == null)
|
|
89
|
+
return title || null;
|
|
90
|
+
if (typeof template === "function")
|
|
91
|
+
return template(title);
|
|
92
|
+
return template.replace("%s", title ?? "");
|
|
93
|
+
};
|
|
94
|
+
function resolveTitleTemplateFromTags(tags) {
|
|
95
|
+
const titleTemplateIdx = tags.findIndex((i) => i.tag === "titleTemplate");
|
|
96
|
+
const titleIdx = tags.findIndex((i) => i.tag === "title");
|
|
97
|
+
if (titleIdx !== -1 && titleTemplateIdx !== -1) {
|
|
98
|
+
const newTitle = renderTitleTemplate(
|
|
99
|
+
tags[titleTemplateIdx].children,
|
|
100
|
+
tags[titleIdx].children
|
|
101
|
+
);
|
|
102
|
+
if (newTitle !== null) {
|
|
103
|
+
tags[titleIdx].children = newTitle || tags[titleIdx].children;
|
|
104
|
+
} else {
|
|
105
|
+
tags = tags.filter((_, i) => i !== titleIdx);
|
|
106
|
+
}
|
|
107
|
+
} else if (titleTemplateIdx !== -1) {
|
|
108
|
+
const newTitle = renderTitleTemplate(
|
|
109
|
+
tags[titleTemplateIdx].children
|
|
110
|
+
);
|
|
111
|
+
if (newTitle !== null) {
|
|
112
|
+
tags[titleTemplateIdx].children = newTitle;
|
|
113
|
+
tags[titleTemplateIdx].tag = "title";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (titleTemplateIdx !== -1)
|
|
117
|
+
tags = tags.filter((_, i) => i !== titleTemplateIdx);
|
|
118
|
+
return tags;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const DedupesTagsPlugin = (options) => {
|
|
122
|
+
options = options || {};
|
|
123
|
+
const dedupeKeys = options.dedupeKeys || ["hid", "vmid", "key"];
|
|
124
|
+
return defineHeadPlugin({
|
|
125
|
+
hooks: {
|
|
126
|
+
"tag:normalise": function({ tag }) {
|
|
127
|
+
dedupeKeys.forEach((key) => {
|
|
128
|
+
if (tag.props[key]) {
|
|
129
|
+
tag.key = tag.props[key];
|
|
130
|
+
delete tag.props[key];
|
|
50
131
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
132
|
+
});
|
|
133
|
+
const dedupe = tag.key ? `${tag.tag}:${tag.key}` : tagDedupeKey(tag);
|
|
134
|
+
if (dedupe)
|
|
135
|
+
tag._d = dedupe;
|
|
136
|
+
},
|
|
137
|
+
"tags:resolve": function(ctx) {
|
|
138
|
+
const deduping = {};
|
|
139
|
+
ctx.tags.forEach((tag, i) => {
|
|
140
|
+
let dedupeKey = tag._d || tag._p || i;
|
|
141
|
+
const dupedTag = deduping[dedupeKey];
|
|
142
|
+
if (dupedTag) {
|
|
143
|
+
let strategy = tag?.tagDuplicateStrategy;
|
|
144
|
+
if (!strategy && (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs"))
|
|
145
|
+
strategy = "merge";
|
|
146
|
+
if (strategy === "merge") {
|
|
147
|
+
const oldProps = dupedTag.props;
|
|
148
|
+
["class", "style"].forEach((key) => {
|
|
149
|
+
if (tag.props[key] && oldProps[key])
|
|
150
|
+
tag.props[key] = `${oldProps[key]} ${tag.props[key]}`;
|
|
151
|
+
});
|
|
152
|
+
deduping[dedupeKey].props = {
|
|
153
|
+
...oldProps,
|
|
154
|
+
...tag.props
|
|
155
|
+
};
|
|
156
|
+
return;
|
|
157
|
+
} else if (tag._e === dupedTag._e) {
|
|
158
|
+
dedupeKey = `${dedupeKey}:entry(${tag._e}:${tag._p})`;
|
|
159
|
+
tag._d = dedupeKey;
|
|
160
|
+
} else {
|
|
161
|
+
tag._p = dupedTag._p;
|
|
162
|
+
}
|
|
163
|
+
if (Object.keys(tag.props).length === 0 && !tag.children) {
|
|
164
|
+
delete deduping[dedupeKey];
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
54
167
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
168
|
+
deduping[dedupeKey] = tag;
|
|
169
|
+
});
|
|
170
|
+
ctx.tags = Object.values(deduping);
|
|
171
|
+
}
|
|
59
172
|
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
173
|
+
});
|
|
174
|
+
};
|
|
62
175
|
|
|
63
|
-
const SortTagsPlugin =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
176
|
+
const SortTagsPlugin = () => {
|
|
177
|
+
return defineHeadPlugin({
|
|
178
|
+
hooks: {
|
|
179
|
+
"tags:resolve": (ctx) => {
|
|
180
|
+
const tagIndexForKey = (key) => ctx.tags.find((tag) => tag._d === key)?._p;
|
|
181
|
+
for (const tag of ctx.tags) {
|
|
182
|
+
if (!tag?.tagPriority)
|
|
183
|
+
continue;
|
|
184
|
+
if (typeof tag.tagPriority === "number") {
|
|
185
|
+
tag._p = tag.tagPriority;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const modifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
|
|
189
|
+
for (const { prefix, offset } of modifiers) {
|
|
190
|
+
if (tag.tagPriority.startsWith(prefix)) {
|
|
191
|
+
const key = tag.tagPriority.replace(prefix, "");
|
|
192
|
+
const index = tagIndexForKey(key);
|
|
193
|
+
if (typeof index !== "undefined")
|
|
194
|
+
tag._p = index + offset;
|
|
195
|
+
}
|
|
81
196
|
}
|
|
82
197
|
}
|
|
198
|
+
ctx.tags.sort((a, b) => a._p - b._p).sort(sortCriticalTags);
|
|
83
199
|
}
|
|
84
|
-
ctx.tags.sort((a, b) => a._p - b._p).sort(sortCriticalTags);
|
|
85
200
|
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
201
|
+
});
|
|
202
|
+
};
|
|
88
203
|
|
|
89
|
-
const TitleTemplatePlugin =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
204
|
+
const TitleTemplatePlugin = () => {
|
|
205
|
+
return defineHeadPlugin({
|
|
206
|
+
hooks: {
|
|
207
|
+
"tags:resolve": (ctx) => {
|
|
208
|
+
ctx.tags = resolveTitleTemplateFromTags(ctx.tags);
|
|
209
|
+
}
|
|
93
210
|
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
211
|
+
});
|
|
212
|
+
};
|
|
96
213
|
|
|
97
|
-
function asArray(value) {
|
|
98
|
-
return Array.isArray(value) ? value : [value];
|
|
99
|
-
}
|
|
100
|
-
const TagConfigKeys = ["tagPosition", "tagPriority", "tagDuplicateStrategy"];
|
|
101
214
|
function hashCode(s) {
|
|
102
215
|
let h = 9;
|
|
103
216
|
for (let i = 0; i < s.length; )
|
|
@@ -105,20 +218,73 @@ function hashCode(s) {
|
|
|
105
218
|
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 7).toLowerCase();
|
|
106
219
|
}
|
|
107
220
|
|
|
108
|
-
const HydratesStatePlugin =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
221
|
+
const HydratesStatePlugin = () => {
|
|
222
|
+
return defineHeadPlugin({
|
|
223
|
+
hooks: {
|
|
224
|
+
"tag:normalise": (ctx) => {
|
|
225
|
+
const { tag, entry } = ctx;
|
|
226
|
+
if (!HasElementTags.includes(tag.tag))
|
|
227
|
+
return;
|
|
228
|
+
if (typeof tag._d === "undefined" && entry._m === "server")
|
|
229
|
+
return;
|
|
230
|
+
const hasChildren = tag.children && tag.children.length > 0;
|
|
231
|
+
tag._s = `data-h-${hashCode(tag._d || tag.tag + (hasChildren ? tag.children : JSON.stringify(tag.props)))}`;
|
|
232
|
+
tag.props[tag._s] = "";
|
|
233
|
+
}
|
|
119
234
|
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const IsClient = typeof window !== "undefined";
|
|
239
|
+
|
|
240
|
+
let activeHead;
|
|
241
|
+
const setActiveHead = (head) => activeHead = head;
|
|
242
|
+
const getActiveHead = () => activeHead;
|
|
243
|
+
|
|
244
|
+
function useHead(input, options = {}) {
|
|
245
|
+
if (options.mode === "server" && IsClient || options.mode === "client" && !IsClient)
|
|
246
|
+
return;
|
|
247
|
+
const head = getActiveHead();
|
|
248
|
+
head.push(input, options);
|
|
249
|
+
}
|
|
250
|
+
function useServerHead(input, options = {}) {
|
|
251
|
+
useHead(input, { ...options, mode: "server" });
|
|
252
|
+
}
|
|
253
|
+
const useTitle = (title) => {
|
|
254
|
+
useHead({ title });
|
|
255
|
+
};
|
|
256
|
+
const useMeta = (meta) => {
|
|
257
|
+
useHead({ meta: [meta] });
|
|
258
|
+
};
|
|
259
|
+
const useLink = (link) => {
|
|
260
|
+
useHead({ link: [link] });
|
|
261
|
+
};
|
|
262
|
+
const useScript = (script) => {
|
|
263
|
+
useHead({ script: [script] });
|
|
264
|
+
};
|
|
265
|
+
const useStyle = (style) => {
|
|
266
|
+
useHead({ style: [style] });
|
|
267
|
+
};
|
|
268
|
+
const useBase = (base) => {
|
|
269
|
+
useHead({ base });
|
|
270
|
+
};
|
|
271
|
+
const useHtmlAttrs = (attrs) => {
|
|
272
|
+
useHead({ htmlAttrs: attrs });
|
|
273
|
+
};
|
|
274
|
+
const useBodyAttrs = (attrs) => {
|
|
275
|
+
useHead({ bodyAttrs: attrs });
|
|
276
|
+
};
|
|
277
|
+
const useTitleTemplate = (titleTemplate) => {
|
|
278
|
+
useHead({ titleTemplate });
|
|
279
|
+
};
|
|
280
|
+
const useNoscript = (noscript) => {
|
|
281
|
+
useHead({ noscript: [noscript] });
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
function asArray(value) {
|
|
285
|
+
return Array.isArray(value) ? value : [value];
|
|
286
|
+
}
|
|
287
|
+
const TagConfigKeys = ["tagPosition", "tagPriority", "tagDuplicateStrategy"];
|
|
122
288
|
|
|
123
289
|
function normaliseTag(tagName, input, entry) {
|
|
124
290
|
const tag = normaliseTag$1(tagName, input, { childrenKeys: ["innerHTML", "textContent"] });
|
|
@@ -159,9 +325,9 @@ function createHead(options = {}) {
|
|
|
159
325
|
if (options.hooks)
|
|
160
326
|
hooks.addHooks(options.hooks);
|
|
161
327
|
const plugins = [
|
|
162
|
-
DedupesTagsPlugin,
|
|
163
|
-
SortTagsPlugin,
|
|
164
|
-
TitleTemplatePlugin
|
|
328
|
+
DedupesTagsPlugin(),
|
|
329
|
+
SortTagsPlugin(),
|
|
330
|
+
TitleTemplatePlugin()
|
|
165
331
|
];
|
|
166
332
|
plugins.push(...options.plugins || []);
|
|
167
333
|
plugins.forEach((plugin) => hooks.addHooks(plugin.hooks || {}));
|
|
@@ -227,39 +393,8 @@ function createHead(options = {}) {
|
|
|
227
393
|
return head;
|
|
228
394
|
}
|
|
229
395
|
|
|
230
|
-
function
|
|
231
|
-
|
|
232
|
-
head.push(input, options);
|
|
396
|
+
function defineHeadPlugin(plugin) {
|
|
397
|
+
return plugin;
|
|
233
398
|
}
|
|
234
|
-
const useTitle = (title) => {
|
|
235
|
-
useHead({ title });
|
|
236
|
-
};
|
|
237
|
-
const useMeta = (meta) => {
|
|
238
|
-
useHead({ meta: [meta] });
|
|
239
|
-
};
|
|
240
|
-
const useLink = (link) => {
|
|
241
|
-
useHead({ link: [link] });
|
|
242
|
-
};
|
|
243
|
-
const useScript = (script) => {
|
|
244
|
-
useHead({ script: [script] });
|
|
245
|
-
};
|
|
246
|
-
const useStyle = (style) => {
|
|
247
|
-
useHead({ style: [style] });
|
|
248
|
-
};
|
|
249
|
-
const useBase = (base) => {
|
|
250
|
-
useHead({ base });
|
|
251
|
-
};
|
|
252
|
-
const useHtmlAttrs = (attrs) => {
|
|
253
|
-
useHead({ htmlAttrs: attrs });
|
|
254
|
-
};
|
|
255
|
-
const useBodyAttrs = (attrs) => {
|
|
256
|
-
useHead({ bodyAttrs: attrs });
|
|
257
|
-
};
|
|
258
|
-
const useTitleTemplate = (titleTemplate) => {
|
|
259
|
-
useHead({ titleTemplate });
|
|
260
|
-
};
|
|
261
|
-
const useNoscript = (noscript) => {
|
|
262
|
-
useHead({ noscript: [noscript] });
|
|
263
|
-
};
|
|
264
399
|
|
|
265
|
-
export { DedupesTagsPlugin, HydratesStatePlugin, SortTagsPlugin, TagConfigKeys, TitleTemplatePlugin, activeHead, asArray, createHead, defineHeadPlugin, getActiveHead,
|
|
400
|
+
export { DedupesTagsPlugin, HydratesStatePlugin, SortTagsPlugin, TagConfigKeys, TitleTemplatePlugin, activeHead, asArray, createHead, defineHeadPlugin, getActiveHead, normaliseEntryTags, normaliseTag, setActiveHead, useBase, useBodyAttrs, useHead, useHtmlAttrs, useLink, useMeta, useNoscript, useScript, useServerHead, useStyle, useTitle, useTitleTemplate };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unhead",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"packageManager": "pnpm@7.14.0",
|
|
6
6
|
"author": "Harlan Wilton <harlan@harlanzw.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -20,14 +20,6 @@
|
|
|
20
20
|
".": {
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
22
22
|
"import": "./dist/index.mjs"
|
|
23
|
-
},
|
|
24
|
-
"./server": {
|
|
25
|
-
"types": "./dist/server.d.ts",
|
|
26
|
-
"import": "./dist/server.mjs"
|
|
27
|
-
},
|
|
28
|
-
"./client": {
|
|
29
|
-
"types": "./dist/client.d.ts",
|
|
30
|
-
"import": "./dist/client.mjs"
|
|
31
23
|
}
|
|
32
24
|
},
|
|
33
25
|
"module": "dist/index.mjs",
|
|
@@ -36,12 +28,15 @@
|
|
|
36
28
|
"dist"
|
|
37
29
|
],
|
|
38
30
|
"dependencies": {
|
|
39
|
-
"@unhead/schema": "0.0
|
|
40
|
-
"hookable": "^5.4.1"
|
|
31
|
+
"@unhead/schema": "0.1.0",
|
|
32
|
+
"hookable": "^5.4.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
41
35
|
"zhead": "1.0.0-beta.4"
|
|
42
36
|
},
|
|
43
37
|
"scripts": {
|
|
44
38
|
"build": "unbuild .",
|
|
45
|
-
"stub": "unbuild . --stub"
|
|
39
|
+
"stub": "unbuild . --stub",
|
|
40
|
+
"export:sizes": "npx export-size . -r"
|
|
46
41
|
}
|
|
47
42
|
}
|
package/dist/client.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { a as HeadClient } from './types-b4318c02.js';
|
|
2
|
-
import 'hookable';
|
|
3
|
-
import '@unhead/schema';
|
|
4
|
-
|
|
5
|
-
interface RenderDomHeadOptions {
|
|
6
|
-
/**
|
|
7
|
-
* Document to use for rendering. Allows stubbing for testing.
|
|
8
|
-
*/
|
|
9
|
-
document?: Document;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Render the head tags to the DOM.
|
|
13
|
-
*/
|
|
14
|
-
declare function renderDOMHead<T extends HeadClient<any>>(head: T, options?: RenderDomHeadOptions): Promise<void>;
|
|
15
|
-
/**
|
|
16
|
-
* Global instance of the dom update promise. Used for debounding head updates.
|
|
17
|
-
*/
|
|
18
|
-
declare let domUpdatePromise: Promise<void> | null;
|
|
19
|
-
/**
|
|
20
|
-
* Queue a debounced update of the DOM head.
|
|
21
|
-
*/
|
|
22
|
-
declare function debouncedRenderDOMHead<T extends HeadClient<any>>(delayedFn: (fn: () => void) => void, head: T, options?: RenderDomHeadOptions): Promise<void>;
|
|
23
|
-
|
|
24
|
-
export { RenderDomHeadOptions, debouncedRenderDOMHead, domUpdatePromise, renderDOMHead };
|
package/dist/client.mjs
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { createElement, TagsWithInnerContent } from 'zhead';
|
|
2
|
-
|
|
3
|
-
function setAttributesWithSideEffects(head, $el, entry, tag) {
|
|
4
|
-
const attrs = tag.props || {};
|
|
5
|
-
const sdeKey = `${tag._p}:attr`;
|
|
6
|
-
Object.entries(entry._sde).filter(([key]) => key.startsWith(sdeKey)).forEach(([key, fn]) => {
|
|
7
|
-
delete entry._sde[key] && fn();
|
|
8
|
-
});
|
|
9
|
-
Object.entries(attrs).forEach(([k, value]) => {
|
|
10
|
-
value = String(value);
|
|
11
|
-
const attrSdeKey = `${sdeKey}:${k}`;
|
|
12
|
-
head._removeQueuedSideEffect(attrSdeKey);
|
|
13
|
-
if (k === "class") {
|
|
14
|
-
for (const c of value.split(" ")) {
|
|
15
|
-
if (!$el.classList.contains(c)) {
|
|
16
|
-
$el.classList.add(c);
|
|
17
|
-
head._removeQueuedSideEffect(`${attrSdeKey}:${c}`);
|
|
18
|
-
entry._sde[`${attrSdeKey}:${c}`] = () => $el.classList.remove(c);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if ($el.getAttribute(k) !== value) {
|
|
24
|
-
$el.setAttribute(k, value);
|
|
25
|
-
if (!k.startsWith("data-h-"))
|
|
26
|
-
entry._sde[attrSdeKey] = () => $el.removeAttribute(k);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function renderDOMHead(head, options = {}) {
|
|
32
|
-
const dom = options.document || window.document;
|
|
33
|
-
const tags = await head.resolveTags();
|
|
34
|
-
await head.hooks.callHook("dom:beforeRender", { head, tags, document: dom });
|
|
35
|
-
for (const tag of tags) {
|
|
36
|
-
const entry = head.headEntries().find((e) => e._i === Number(tag._e));
|
|
37
|
-
const sdeKey = `${tag._s || tag._p}:el`;
|
|
38
|
-
const $newEl = createElement(tag, dom);
|
|
39
|
-
const $el = tag._s ? dom.querySelector(`[${tag._s}]`) : null;
|
|
40
|
-
const renderCtx = { tag, document: dom, head };
|
|
41
|
-
await head.hooks.callHook("dom:renderTag", renderCtx);
|
|
42
|
-
if ($el) {
|
|
43
|
-
head._removeQueuedSideEffect(sdeKey);
|
|
44
|
-
if ($newEl.isEqualNode($el))
|
|
45
|
-
continue;
|
|
46
|
-
if (Object.keys(tag.props).length === 0) {
|
|
47
|
-
$el.remove();
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
setAttributesWithSideEffects(head, $el, entry, tag);
|
|
51
|
-
if (TagsWithInnerContent.includes(tag.tag))
|
|
52
|
-
$el.innerHTML = tag.children || "";
|
|
53
|
-
entry._sde[sdeKey] = () => $el?.remove();
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (tag.tag === "title" && tag.children) {
|
|
57
|
-
dom.title = tag.children;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
|
|
61
|
-
setAttributesWithSideEffects(head, dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"], entry, tag);
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
switch (tag.tagPosition) {
|
|
65
|
-
case "bodyClose":
|
|
66
|
-
dom.body.appendChild($newEl);
|
|
67
|
-
break;
|
|
68
|
-
case "bodyOpen":
|
|
69
|
-
dom.body.insertBefore($newEl, dom.body.firstChild);
|
|
70
|
-
break;
|
|
71
|
-
case "head":
|
|
72
|
-
default:
|
|
73
|
-
dom.head.appendChild($newEl);
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
entry._sde[sdeKey] = () => $newEl?.remove();
|
|
77
|
-
}
|
|
78
|
-
head._flushQueuedSideEffects();
|
|
79
|
-
}
|
|
80
|
-
let domUpdatePromise = null;
|
|
81
|
-
async function debouncedRenderDOMHead(delayedFn, head, options = {}) {
|
|
82
|
-
function doDomUpdate() {
|
|
83
|
-
domUpdatePromise = null;
|
|
84
|
-
return renderDOMHead(head, options);
|
|
85
|
-
}
|
|
86
|
-
return domUpdatePromise = domUpdatePromise || new Promise((resolve) => delayedFn(() => resolve(doDomUpdate())));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export { debouncedRenderDOMHead, domUpdatePromise, renderDOMHead };
|
package/dist/server.d.ts
DELETED
package/dist/server.mjs
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ssrRenderTags } from 'zhead';
|
|
2
|
-
|
|
3
|
-
async function renderSSRHead(ctx) {
|
|
4
|
-
const tags = await ctx.resolveTags();
|
|
5
|
-
const beforeRenderCtx = { tags };
|
|
6
|
-
await ctx.hooks.callHook("ssr:beforeRender", beforeRenderCtx);
|
|
7
|
-
const html = ssrRenderTags(beforeRenderCtx.tags);
|
|
8
|
-
const renderCXx = { tags, html };
|
|
9
|
-
await ctx.hooks.callHook("ssr:render", renderCXx);
|
|
10
|
-
return renderCXx.html;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export { renderSSRHead };
|
package/dist/types-b4318c02.d.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { NestedHooks, Hookable } from 'hookable';
|
|
2
|
-
import { Head, HeadTag } from '@unhead/schema';
|
|
3
|
-
|
|
4
|
-
declare type HeadPlugin<O> = Omit<CreateHeadOptions<O>, 'plugins'>;
|
|
5
|
-
declare function defineHeadPlugin<O = Head>(plugin: HeadPlugin<O>): HeadPlugin<O>;
|
|
6
|
-
|
|
7
|
-
interface SSRHeadPayload {
|
|
8
|
-
headTags: string;
|
|
9
|
-
bodyTags: string;
|
|
10
|
-
bodyTagsOpen: string;
|
|
11
|
-
htmlAttrs: string;
|
|
12
|
-
bodyAttrs: string;
|
|
13
|
-
}
|
|
14
|
-
declare function renderSSRHead<T extends HeadClient<any>>(ctx: T): Promise<SSRHeadPayload>;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* An active head entry provides an API to manipulate it.
|
|
18
|
-
*/
|
|
19
|
-
interface ActiveHeadEntry<T> {
|
|
20
|
-
/**
|
|
21
|
-
* Updates the entry with new input.
|
|
22
|
-
*
|
|
23
|
-
* Will first clear any side effects for previous input.
|
|
24
|
-
*/
|
|
25
|
-
patch: (resolvedInput: T) => void;
|
|
26
|
-
/**
|
|
27
|
-
* Dispose the entry, removing it from the active head.
|
|
28
|
-
*
|
|
29
|
-
* Will queue side effects for removal.
|
|
30
|
-
*/
|
|
31
|
-
dispose: () => void;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Side effects are mapped with a key and their cleanup function.
|
|
35
|
-
*
|
|
36
|
-
* For example `meta:data-h-4h46h465`: () => { document.querySelector('meta[data-h-4h46h465]').remove() }
|
|
37
|
-
*/
|
|
38
|
-
declare type SideEffectsRecord = Record<string, () => void>;
|
|
39
|
-
declare type RuntimeMode = 'server' | 'client' | 'all';
|
|
40
|
-
interface HeadEntry<T> {
|
|
41
|
-
/**
|
|
42
|
-
* User provided input for the entry.
|
|
43
|
-
*/
|
|
44
|
-
input: T;
|
|
45
|
-
/**
|
|
46
|
-
* The mode that the entry should be used in.
|
|
47
|
-
*
|
|
48
|
-
* @internal
|
|
49
|
-
*/
|
|
50
|
-
_m?: RuntimeMode;
|
|
51
|
-
/**
|
|
52
|
-
* Head entry index
|
|
53
|
-
*
|
|
54
|
-
* @internal
|
|
55
|
-
*/
|
|
56
|
-
_i: number;
|
|
57
|
-
/**
|
|
58
|
-
* Side effects
|
|
59
|
-
*
|
|
60
|
-
* @internal
|
|
61
|
-
*/
|
|
62
|
-
_sde: SideEffectsRecord;
|
|
63
|
-
}
|
|
64
|
-
declare type HookResult = Promise<void> | void;
|
|
65
|
-
interface DomRenderTagContext {
|
|
66
|
-
head: HeadClient;
|
|
67
|
-
tag: HeadTag;
|
|
68
|
-
document: Document;
|
|
69
|
-
}
|
|
70
|
-
interface EntryResolveCtx<T> {
|
|
71
|
-
tags: HeadTag[];
|
|
72
|
-
entries: HeadEntry<T>[];
|
|
73
|
-
}
|
|
74
|
-
interface HeadHooks<T> {
|
|
75
|
-
'entries:resolve': (ctx: EntryResolveCtx<T>) => HookResult;
|
|
76
|
-
'tag:normalise': (ctx: {
|
|
77
|
-
tag: HeadTag;
|
|
78
|
-
entry: HeadEntry<T>;
|
|
79
|
-
}) => HookResult;
|
|
80
|
-
'tags:resolve': (ctx: {
|
|
81
|
-
tags: HeadTag[];
|
|
82
|
-
}) => HookResult;
|
|
83
|
-
'dom:beforeRender': (ctx: {
|
|
84
|
-
head: HeadClient;
|
|
85
|
-
tags: HeadTag[];
|
|
86
|
-
document: Document;
|
|
87
|
-
}) => HookResult;
|
|
88
|
-
'dom:renderTag': (ctx: DomRenderTagContext) => HookResult;
|
|
89
|
-
'ssr:beforeRender': (ctx: {
|
|
90
|
-
tags: HeadTag[];
|
|
91
|
-
}) => HookResult;
|
|
92
|
-
'ssr:render': (ctx: {
|
|
93
|
-
tags: HeadTag[];
|
|
94
|
-
html: SSRHeadPayload;
|
|
95
|
-
}) => HookResult;
|
|
96
|
-
}
|
|
97
|
-
interface CreateHeadOptions<T> {
|
|
98
|
-
plugins?: HeadPlugin<any>[];
|
|
99
|
-
hooks?: NestedHooks<HeadHooks<T>>;
|
|
100
|
-
}
|
|
101
|
-
interface HeadEntryOptions {
|
|
102
|
-
mode?: RuntimeMode;
|
|
103
|
-
}
|
|
104
|
-
interface HeadClient<T = Head> {
|
|
105
|
-
/**
|
|
106
|
-
* The active head entries.
|
|
107
|
-
*/
|
|
108
|
-
headEntries: () => HeadEntry<T>[];
|
|
109
|
-
/**
|
|
110
|
-
* Create a new head entry.
|
|
111
|
-
*/
|
|
112
|
-
push: (entry: T, options?: HeadEntryOptions) => ActiveHeadEntry<T>;
|
|
113
|
-
/**
|
|
114
|
-
* Resolve tags from head entries.
|
|
115
|
-
*/
|
|
116
|
-
resolveTags: () => Promise<HeadTag[]>;
|
|
117
|
-
/**
|
|
118
|
-
* Exposed hooks for easier extension.
|
|
119
|
-
*/
|
|
120
|
-
hooks: Hookable<HeadHooks<T>>;
|
|
121
|
-
/**
|
|
122
|
-
* @internal
|
|
123
|
-
*/
|
|
124
|
-
_removeQueuedSideEffect: (key: string) => void;
|
|
125
|
-
/**
|
|
126
|
-
* @internal
|
|
127
|
-
*/
|
|
128
|
-
_flushQueuedSideEffects: () => void;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export { ActiveHeadEntry as A, CreateHeadOptions as C, DomRenderTagContext as D, EntryResolveCtx as E, HeadPlugin as H, RuntimeMode as R, SideEffectsRecord as S, HeadClient as a, HeadEntryOptions as b, HeadEntry as c, defineHeadPlugin as d, HookResult as e, HeadHooks as f, SSRHeadPayload as g, renderSSRHead as r };
|