unhead 1.2.2 → 1.3.0-beta.1

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.cjs CHANGED
@@ -4,331 +4,355 @@ const hookable = require('hookable');
4
4
  const dom = require('@unhead/dom');
5
5
  const shared = require('@unhead/shared');
6
6
 
7
- const TAG_WEIGHTS = {
8
- // tags
9
- base: -1,
10
- title: 1
11
- };
12
- const TAG_ALIASES = {
13
- // relative scores to their default values
14
- critical: -8,
15
- high: -1,
16
- low: 2
17
- };
18
- function tagWeight(tag) {
19
- let weight = 10;
20
- const priority = tag.tagPriority;
21
- if (typeof priority === "number")
22
- return priority;
23
- if (tag.tag === "meta") {
24
- if (tag.props.charset)
25
- weight = -2;
26
- if (tag.props["http-equiv"] === "content-security-policy")
27
- weight = 0;
28
- } else if (tag.tag == "link" && tag.props.rel === "preconnect") {
29
- weight = 2;
30
- } else if (tag.tag in TAG_WEIGHTS) {
31
- weight = TAG_WEIGHTS[tag.tag];
32
- }
33
- if (typeof priority === "string" && priority in TAG_ALIASES) {
34
- return weight + TAG_ALIASES[priority];
35
- }
36
- return weight;
37
- }
38
- const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
39
- function SortTagsPlugin() {
40
- return shared.defineHeadPlugin({
41
- hooks: {
42
- "tags:resolve": (ctx) => {
43
- const tagPositionForKey = (key) => ctx.tags.find((tag) => tag._d === key)?._p;
44
- for (const { prefix, offset } of SortModifiers) {
45
- for (const tag of ctx.tags.filter((tag2) => typeof tag2.tagPriority === "string" && tag2.tagPriority.startsWith(prefix))) {
46
- const position = tagPositionForKey(
47
- tag.tagPriority.replace(prefix, "")
48
- );
49
- if (typeof position !== "undefined")
50
- tag._p = position + offset;
7
+ const UsesMergeStrategy = ["templateParams", "htmlAttrs", "bodyAttrs"];
8
+ const DedupePlugin = shared.defineHeadPlugin({
9
+ hooks: {
10
+ "tag:normalise": function({ tag }) {
11
+ ["hid", "vmid", "key"].forEach((key) => {
12
+ if (tag.props[key]) {
13
+ tag.key = tag.props[key];
14
+ delete tag.props[key];
15
+ }
16
+ });
17
+ const generatedKey = shared.tagDedupeKey(tag);
18
+ const dedupe = generatedKey || (tag.key ? `${tag.tag}:${tag.key}` : false);
19
+ if (dedupe)
20
+ tag._d = dedupe;
21
+ },
22
+ "tags:resolve": function(ctx) {
23
+ const deduping = {};
24
+ ctx.tags.forEach((tag) => {
25
+ const dedupeKey = (tag.key ? `${tag.tag}:${tag.key}` : tag._d) || tag._p;
26
+ const dupedTag = deduping[dedupeKey];
27
+ if (dupedTag) {
28
+ let strategy = tag?.tagDuplicateStrategy;
29
+ if (!strategy && UsesMergeStrategy.includes(tag.tag))
30
+ strategy = "merge";
31
+ if (strategy === "merge") {
32
+ const oldProps = dupedTag.props;
33
+ ["class", "style"].forEach((key) => {
34
+ if (tag.props[key] && oldProps[key]) {
35
+ if (key === "style" && !oldProps[key].endsWith(";"))
36
+ oldProps[key] += ";";
37
+ tag.props[key] = `${oldProps[key]} ${tag.props[key]}`;
38
+ }
39
+ });
40
+ deduping[dedupeKey].props = {
41
+ ...oldProps,
42
+ ...tag.props
43
+ };
44
+ return;
45
+ } else if (tag._e === dupedTag._e) {
46
+ dupedTag._duped = dupedTag._duped || [];
47
+ tag._d = `${dupedTag._d}:${dupedTag._duped.length + 1}`;
48
+ dupedTag._duped.push(tag);
49
+ return;
50
+ } else if (shared.tagWeight(tag) > shared.tagWeight(dupedTag)) {
51
+ return;
51
52
  }
52
53
  }
53
- ctx.tags.sort((a, b) => a._p - b._p).sort((a, b) => tagWeight(a) - tagWeight(b));
54
- }
54
+ const propCount = Object.keys(tag.props).length + (tag.innerHTML ? 1 : 0) + (tag.textContent ? 1 : 0);
55
+ if (shared.HasElementTags.includes(tag.tag) && propCount === 0) {
56
+ delete deduping[dedupeKey];
57
+ return;
58
+ }
59
+ deduping[dedupeKey] = tag;
60
+ });
61
+ const newTags = [];
62
+ Object.values(deduping).forEach((tag) => {
63
+ const dupes = tag._duped;
64
+ delete tag._duped;
65
+ newTags.push(tag);
66
+ if (dupes)
67
+ newTags.push(...dupes);
68
+ });
69
+ ctx.tags = newTags;
55
70
  }
71
+ }
72
+ });
73
+
74
+ const ValidEventTags = ["script", "link", "bodyAttrs"];
75
+ function stripEventHandlers(tag) {
76
+ const props = {};
77
+ const eventHandlers = {};
78
+ Object.entries(tag.props).forEach(([key, value]) => {
79
+ if (key.startsWith("on") && typeof value === "function")
80
+ eventHandlers[key] = value;
81
+ else
82
+ props[key] = value;
56
83
  });
84
+ return { props, eventHandlers };
57
85
  }
58
-
59
- function TitleTemplatePlugin() {
60
- return shared.defineHeadPlugin({
61
- hooks: {
62
- "tags:resolve": (ctx) => {
63
- const { tags } = ctx;
64
- let titleTemplateIdx = tags.findIndex((i) => i.tag === "titleTemplate");
65
- const titleIdx = tags.findIndex((i) => i.tag === "title");
66
- if (titleIdx !== -1 && titleTemplateIdx !== -1) {
67
- const newTitle = shared.resolveTitleTemplate(
68
- tags[titleTemplateIdx].textContent,
69
- tags[titleIdx].textContent
70
- );
71
- if (newTitle !== null) {
72
- tags[titleIdx].textContent = newTitle || tags[titleIdx].textContent;
73
- } else {
74
- delete tags[titleIdx];
75
- }
76
- } else if (titleTemplateIdx !== -1) {
77
- const newTitle = shared.resolveTitleTemplate(
78
- tags[titleTemplateIdx].textContent
79
- );
80
- if (newTitle !== null) {
81
- tags[titleTemplateIdx].textContent = newTitle;
82
- tags[titleTemplateIdx].tag = "title";
83
- titleTemplateIdx = -1;
84
- }
86
+ const EventHandlersPlugin = shared.defineHeadPlugin({
87
+ hooks: {
88
+ "ssr:render": function(ctx) {
89
+ ctx.tags = ctx.tags.map((tag) => {
90
+ if (!ValidEventTags.includes(tag.tag))
91
+ return tag;
92
+ if (!Object.entries(tag.props).find(([key, value]) => key.startsWith("on") && typeof value === "function"))
93
+ return tag;
94
+ tag.props = stripEventHandlers(tag).props;
95
+ return tag;
96
+ });
97
+ },
98
+ "tags:resolve": function(ctx) {
99
+ ctx.tags = ctx.tags.map((tag) => {
100
+ if (!ValidEventTags.includes(tag.tag))
101
+ return tag;
102
+ const { props, eventHandlers } = stripEventHandlers(tag);
103
+ if (Object.keys(eventHandlers).length) {
104
+ tag.props = props;
105
+ tag._eventHandlers = eventHandlers;
85
106
  }
86
- if (titleTemplateIdx !== -1) {
87
- delete tags[titleTemplateIdx];
107
+ return tag;
108
+ });
109
+ },
110
+ "dom:renderTag": function(ctx, dom, track) {
111
+ if (!ctx.tag._eventHandlers)
112
+ return;
113
+ const $eventListenerTarget = ctx.tag.tag === "bodyAttrs" ? dom.defaultView : ctx.$el;
114
+ Object.entries(ctx.tag._eventHandlers).forEach(([k, value]) => {
115
+ const sdeKey = `${ctx.tag._d || ctx.tag._p}:${k}`;
116
+ const eventName = k.slice(2).toLowerCase();
117
+ const eventDedupeKey = `data-h-${eventName}`;
118
+ track(ctx.id, sdeKey, () => {
119
+ });
120
+ if (ctx.$el.hasAttribute(eventDedupeKey))
121
+ return;
122
+ const handler = value;
123
+ ctx.$el.setAttribute(eventDedupeKey, "");
124
+ $eventListenerTarget.addEventListener(eventName, handler);
125
+ if (ctx.entry) {
126
+ track(ctx.id, sdeKey, () => {
127
+ $eventListenerTarget.removeEventListener(eventName, handler);
128
+ ctx.$el.removeAttribute(eventDedupeKey);
129
+ });
88
130
  }
89
- ctx.tags = tags.filter(Boolean);
131
+ });
132
+ }
133
+ }
134
+ });
135
+
136
+ const DupeableTags = ["link", "style", "script", "noscript"];
137
+ const HashKeyedPLugin = shared.defineHeadPlugin({
138
+ hooks: {
139
+ "tag:normalise": ({ tag }) => {
140
+ if (tag.key && DupeableTags.includes(tag.tag)) {
141
+ tag.props["data-hid"] = tag._h = shared.hashCode(tag.key);
90
142
  }
91
143
  }
92
- });
93
- }
144
+ }
145
+ });
94
146
 
95
- function DeprecatedTagAttrPlugin() {
96
- return shared.defineHeadPlugin({
97
- hooks: {
98
- "tag:normalise": function({ tag }) {
99
- if (typeof tag.props.body !== "undefined") {
100
- tag.tagPosition = "bodyClose";
101
- delete tag.props.body;
147
+ const SortPLugin = shared.defineHeadPlugin({
148
+ hooks: {
149
+ "tags:resolve": (ctx) => {
150
+ const tagPositionForKey = (key) => ctx.tags.find((tag) => tag._d === key)?._p;
151
+ for (const { prefix, offset } of shared.SortModifiers) {
152
+ for (const tag of ctx.tags.filter((tag2) => typeof tag2.tagPriority === "string" && tag2.tagPriority.startsWith(prefix))) {
153
+ const position = tagPositionForKey(
154
+ tag.tagPriority.replace(prefix, "")
155
+ );
156
+ if (typeof position !== "undefined")
157
+ tag._p = position + offset;
102
158
  }
103
159
  }
160
+ ctx.tags.sort((a, b) => a._p - b._p).sort((a, b) => shared.tagWeight(a) - shared.tagWeight(b));
104
161
  }
105
- });
106
- }
162
+ }
163
+ });
107
164
 
108
- const DupeableTags = ["link", "style", "script", "noscript"];
109
- function ProvideTagHashPlugin() {
110
- return shared.defineHeadPlugin({
111
- hooks: {
112
- "tag:normalise": ({ tag, resolvedOptions }) => {
113
- if (resolvedOptions.experimentalHashHydration === true) {
114
- tag._h = shared.hashTag(tag);
115
- }
116
- if (tag.key && DupeableTags.includes(tag.tag)) {
117
- tag._h = shared.hashCode(tag.key);
118
- tag.props[`data-h-${tag._h}`] = "";
165
+ const TemplateParamsPlugin = shared.defineHeadPlugin({
166
+ hooks: {
167
+ "tags:resolve": (ctx) => {
168
+ const { tags } = ctx;
169
+ const title = tags.find((tag) => tag.tag === "title")?.textContent;
170
+ const idx = tags.findIndex((tag) => tag.tag === "templateParams");
171
+ const params = idx !== -1 ? tags[idx].props : {};
172
+ params.separator = params.separator || "|";
173
+ params.pageTitle = shared.processTemplateParams(params.pageTitle || title || "", params);
174
+ for (const tag of tags) {
175
+ if (["titleTemplate", "title"].includes(tag.tag) && typeof tag.textContent === "string") {
176
+ tag.textContent = shared.processTemplateParams(tag.textContent, params);
177
+ } else if (tag.tag === "meta" && typeof tag.props.content === "string") {
178
+ tag.props.content = shared.processTemplateParams(tag.props.content, params);
179
+ } else if (tag.tag === "link" && typeof tag.props.href === "string") {
180
+ tag.props.href = shared.processTemplateParams(tag.props.href, params);
181
+ } else if (tag.tag === "script" && ["application/json", "application/ld+json"].includes(tag.props.type) && typeof tag.innerHTML === "string") {
182
+ try {
183
+ tag.innerHTML = JSON.stringify(JSON.parse(tag.innerHTML), (key, val) => {
184
+ if (typeof val === "string")
185
+ return shared.processTemplateParams(val, params);
186
+ return val;
187
+ });
188
+ } catch {
189
+ }
119
190
  }
120
191
  }
192
+ ctx.tags = tags.filter((tag) => tag.tag !== "templateParams");
121
193
  }
122
- });
123
- }
194
+ }
195
+ });
124
196
 
125
- const ValidEventTags = ["script", "link", "bodyAttrs"];
126
- function EventHandlersPlugin() {
127
- const stripEventHandlers = (mode, tag) => {
128
- const props = {};
129
- const eventHandlers = {};
130
- Object.entries(tag.props).forEach(([key, value]) => {
131
- if (key.startsWith("on") && typeof value === "function")
132
- eventHandlers[key] = value;
133
- else
134
- props[key] = value;
135
- });
136
- let delayedSrc;
137
- if (mode === "dom" && tag.tag === "script" && typeof props.src === "string" && typeof eventHandlers.onload !== "undefined") {
138
- delayedSrc = props.src;
139
- delete props.src;
140
- }
141
- return { props, eventHandlers, delayedSrc };
142
- };
143
- return shared.defineHeadPlugin({
144
- hooks: {
145
- "ssr:render": function(ctx) {
146
- ctx.tags = ctx.tags.map((tag) => {
147
- if (!ValidEventTags.includes(tag.tag))
148
- return tag;
149
- if (!Object.entries(tag.props).find(([key, value]) => key.startsWith("on") && typeof value === "function"))
150
- return tag;
151
- tag.props = stripEventHandlers("ssr", tag).props;
152
- return tag;
153
- });
154
- },
155
- "dom:beforeRenderTag": function(ctx) {
156
- if (!ValidEventTags.includes(ctx.tag.tag))
157
- return;
158
- if (!Object.entries(ctx.tag.props).find(([key, value]) => key.startsWith("on") && typeof value === "function"))
159
- return;
160
- const { props, eventHandlers, delayedSrc } = stripEventHandlers("dom", ctx.tag);
161
- if (!Object.keys(eventHandlers).length)
162
- return;
163
- ctx.tag.props = props;
164
- ctx.tag._eventHandlers = eventHandlers;
165
- ctx.tag._delayedSrc = delayedSrc;
166
- },
167
- "dom:renderTag": function(ctx) {
168
- const $el = ctx.$el;
169
- if (!ctx.tag._eventHandlers || !$el)
170
- return;
171
- const $eventListenerTarget = ctx.tag.tag === "bodyAttrs" && typeof window !== "undefined" ? window : $el;
172
- Object.entries(ctx.tag._eventHandlers).forEach(([k, value]) => {
173
- const sdeKey = `${ctx.tag._d || ctx.tag._p}:${k}`;
174
- const eventName = k.slice(2).toLowerCase();
175
- const eventDedupeKey = `data-h-${eventName}`;
176
- ctx.markSideEffect(sdeKey, () => {
177
- });
178
- if ($el.hasAttribute(eventDedupeKey))
179
- return;
180
- const handler = value;
181
- $el.setAttribute(eventDedupeKey, "");
182
- $eventListenerTarget.addEventListener(eventName, handler);
183
- if (ctx.entry) {
184
- ctx.entry._sde[sdeKey] = () => {
185
- $eventListenerTarget.removeEventListener(eventName, handler);
186
- $el.removeAttribute(eventDedupeKey);
187
- };
188
- }
189
- });
190
- if (ctx.tag._delayedSrc) {
191
- $el.setAttribute("src", ctx.tag._delayedSrc);
197
+ const TitleTemplatePlugin = shared.defineHeadPlugin({
198
+ hooks: {
199
+ "tags:resolve": (ctx) => {
200
+ const { tags } = ctx;
201
+ let titleTemplateIdx = tags.findIndex((i) => i.tag === "titleTemplate");
202
+ const titleIdx = tags.findIndex((i) => i.tag === "title");
203
+ if (titleIdx !== -1 && titleTemplateIdx !== -1) {
204
+ const newTitle = shared.resolveTitleTemplate(
205
+ tags[titleTemplateIdx].textContent,
206
+ tags[titleIdx].textContent
207
+ );
208
+ if (newTitle !== null) {
209
+ tags[titleIdx].textContent = newTitle || tags[titleIdx].textContent;
210
+ } else {
211
+ delete tags[titleIdx];
212
+ }
213
+ } else if (titleTemplateIdx !== -1) {
214
+ const newTitle = shared.resolveTitleTemplate(
215
+ tags[titleTemplateIdx].textContent
216
+ );
217
+ if (newTitle !== null) {
218
+ tags[titleTemplateIdx].textContent = newTitle;
219
+ tags[titleTemplateIdx].tag = "title";
220
+ titleTemplateIdx = -1;
192
221
  }
193
222
  }
223
+ if (titleTemplateIdx !== -1) {
224
+ delete tags[titleTemplateIdx];
225
+ }
226
+ ctx.tags = tags.filter(Boolean);
194
227
  }
228
+ }
229
+ });
230
+
231
+ let activeHead;
232
+ function createHead(options = {}) {
233
+ const head = createHeadCore(options);
234
+ if (!head.ssr)
235
+ head.use(dom.PatchDomOnEntryUpdatesPlugin());
236
+ return activeHead = head;
237
+ }
238
+ function createServerHead(options = {}) {
239
+ const head = createHeadCore({
240
+ ...options,
241
+ mode: "server"
195
242
  });
243
+ return activeHead = head;
196
244
  }
197
-
198
- const UsesMergeStrategy = ["templateParams", "htmlAttrs", "bodyAttrs"];
199
- function DedupesTagsPlugin() {
200
- return shared.defineHeadPlugin({
201
- hooks: {
202
- "tag:normalise": function({ tag }) {
203
- ["hid", "vmid", "key"].forEach((key) => {
204
- if (tag.props[key]) {
205
- tag.key = tag.props[key];
206
- delete tag.props[key];
207
- }
208
- });
209
- const generatedKey = shared.tagDedupeKey(tag);
210
- const dedupe = generatedKey || (tag.key ? `${tag.tag}:${tag.key}` : false);
211
- if (dedupe)
212
- tag._d = dedupe;
213
- },
214
- "tags:resolve": function(ctx) {
215
- const deduping = {};
216
- ctx.tags.forEach((tag) => {
217
- const dedupeKey = (tag.key ? `${tag.tag}:${tag.key}` : tag._d) || tag._p;
218
- const dupedTag = deduping[dedupeKey];
219
- if (dupedTag) {
220
- let strategy = tag?.tagDuplicateStrategy;
221
- if (!strategy && UsesMergeStrategy.includes(tag.tag))
222
- strategy = "merge";
223
- if (strategy === "merge") {
224
- const oldProps = dupedTag.props;
225
- ["class", "style"].forEach((key) => {
226
- if (tag.props[key] && oldProps[key]) {
227
- if (key === "style" && !oldProps[key].endsWith(";"))
228
- oldProps[key] += ";";
229
- tag.props[key] = `${oldProps[key]} ${tag.props[key]}`;
230
- }
231
- });
232
- deduping[dedupeKey].props = {
233
- ...oldProps,
234
- ...tag.props
235
- };
236
- return;
237
- } else if (tag._e === dupedTag._e) {
238
- dupedTag._duped = dupedTag._duped || [];
239
- tag._d = `${dupedTag._d}:${dupedTag._duped.length + 1}`;
240
- dupedTag._duped.push(tag);
241
- return;
242
- } else if (tagWeight(tag) > tagWeight(dupedTag)) {
243
- return;
245
+ function createHeadCore(options = {}) {
246
+ const hooks = hookable.createHooks();
247
+ hooks.addHooks(options.hooks || {});
248
+ options.plugins = [
249
+ DedupePlugin,
250
+ EventHandlersPlugin,
251
+ HashKeyedPLugin,
252
+ SortPLugin,
253
+ TemplateParamsPlugin,
254
+ TitleTemplatePlugin,
255
+ ...options?.plugins || []
256
+ ];
257
+ options.plugins.forEach((p) => hooks.addHooks(p.hooks || {}));
258
+ options.document = options.document || (shared.IsBrowser ? document : void 0);
259
+ const ssr = !options.document;
260
+ const updated = () => hooks.callHook("entries:updated", head);
261
+ let entryCount = 0;
262
+ let entries = [];
263
+ const head = {
264
+ resolvedOptions: options,
265
+ hooks,
266
+ headEntries() {
267
+ return entries;
268
+ },
269
+ use(plugin) {
270
+ if (plugin.hooks)
271
+ hooks.addHooks(plugin.hooks);
272
+ },
273
+ push(input, entryOptions) {
274
+ const activeEntry = {
275
+ _i: entryCount++,
276
+ input,
277
+ ...entryOptions
278
+ };
279
+ const mode = activeEntry?.mode || options.mode;
280
+ if (mode)
281
+ activeEntry.mode = mode;
282
+ if (options.mode === "server" && ssr || options.mode === "client" && !ssr || !options.mode) {
283
+ entries.push(activeEntry);
284
+ updated();
285
+ }
286
+ return {
287
+ dispose() {
288
+ entries = entries.filter((e) => e._i !== activeEntry._i);
289
+ hooks.callHook("entries:updated", head);
290
+ updated();
291
+ },
292
+ // a patch is the same as creating a new entry, just a nice DX
293
+ patch(input2) {
294
+ entries = entries.map((e) => {
295
+ if (e._i === activeEntry._i) {
296
+ activeEntry.input = e.input = input2;
244
297
  }
298
+ return e;
299
+ });
300
+ updated();
301
+ }
302
+ };
303
+ },
304
+ async resolveTags() {
305
+ const resolveCtx = { tags: [], entries: [...entries] };
306
+ await hooks.callHook("entries:resolve", resolveCtx);
307
+ for (const entry of resolveCtx.entries) {
308
+ const resolved = entry.resolvedInput || entry.input;
309
+ entry.resolvedInput = await (entry.transform ? entry.transform(resolved) : resolved);
310
+ if (entry.resolvedInput) {
311
+ for (const tag of await shared.normaliseEntryTags(entry)) {
312
+ const tagCtx = { tag, entry, resolvedOptions: head.resolvedOptions };
313
+ await hooks.callHook("tag:normalise", tagCtx);
314
+ resolveCtx.tags.push(tagCtx.tag);
245
315
  }
246
- const propCount = Object.keys(tag.props).length + (tag.innerHTML ? 1 : 0) + (tag.textContent ? 1 : 0);
247
- if (shared.HasElementTags.includes(tag.tag) && propCount === 0) {
248
- delete deduping[dedupeKey];
249
- return;
250
- }
251
- deduping[dedupeKey] = tag;
252
- });
253
- const newTags = [];
254
- Object.values(deduping).forEach((tag) => {
255
- const dupes = tag._duped;
256
- delete tag._duped;
257
- newTags.push(tag);
258
- if (dupes)
259
- newTags.push(...dupes);
260
- });
261
- ctx.tags = newTags;
316
+ }
262
317
  }
263
- }
264
- });
318
+ await hooks.callHook("tags:beforeResolve", resolveCtx);
319
+ await hooks.callHook("tags:resolve", resolveCtx);
320
+ return resolveCtx.tags;
321
+ },
322
+ ssr
323
+ };
324
+ head.hooks.callHook("init", head);
325
+ return head;
265
326
  }
266
327
 
267
- function processTemplateParams(s, p) {
268
- if (typeof s !== "string")
269
- return s;
270
- function sub(token) {
271
- if (["s", "pageTitle"].includes(token))
272
- return p.pageTitle;
273
- let val;
274
- if (token.includes(".")) {
275
- val = token.split(".").reduce((acc, key) => acc ? acc[key] || void 0 : void 0, p);
276
- } else {
277
- val = p[token];
278
- }
279
- return typeof val !== "undefined" ? val || "" : false;
280
- }
281
- let decoded = s;
282
- try {
283
- decoded = decodeURI(s);
284
- } catch {
285
- }
286
- const tokens = (decoded.match(/%(\w+\.+\w+)|%(\w+)/g) || []).sort().reverse();
287
- tokens.forEach((token) => {
288
- const re = sub(token.slice(1));
289
- if (typeof re === "string") {
290
- s = s.replace(new RegExp(`\\${token}(\\W|$)`, "g"), (_, args) => `${re}${args}`).trim();
291
- }
292
- });
293
- const sep = p.separator;
294
- if (s.includes(sep)) {
295
- if (s.endsWith(sep))
296
- s = s.slice(0, -sep.length).trim();
297
- if (s.startsWith(sep))
298
- s = s.slice(sep.length).trim();
299
- s = s.replace(new RegExp(`\\${sep}\\s*\\${sep}`, "g"), sep);
300
- }
301
- return s;
302
- }
303
- function TemplateParamsPlugin() {
328
+ function HashHydrationPlugin() {
329
+ let prevHash = false;
330
+ let dirty = false;
331
+ let head;
304
332
  return shared.defineHeadPlugin({
305
333
  hooks: {
306
- "tags:resolve": (ctx) => {
307
- const { tags } = ctx;
308
- const title = tags.find((tag) => tag.tag === "title")?.textContent;
309
- const idx = tags.findIndex((tag) => tag.tag === "templateParams");
310
- const params = idx !== -1 ? tags[idx].props : {};
311
- params.separator = params.separator || "|";
312
- params.pageTitle = processTemplateParams(params.pageTitle || title || "", params);
313
- for (const tag of tags) {
314
- if (["titleTemplate", "title"].includes(tag.tag) && typeof tag.textContent === "string") {
315
- tag.textContent = processTemplateParams(tag.textContent, params);
316
- } else if (tag.tag === "meta" && typeof tag.props.content === "string") {
317
- tag.props.content = processTemplateParams(tag.props.content, params);
318
- } else if (tag.tag === "link" && typeof tag.props.href === "string") {
319
- tag.props.href = processTemplateParams(tag.props.href, params);
320
- } else if (tag.tag === "script" && ["application/json", "application/ld+json"].includes(tag.props.type) && typeof tag.innerHTML === "string") {
321
- try {
322
- tag.innerHTML = JSON.stringify(JSON.parse(tag.innerHTML), (key, val) => {
323
- if (typeof val === "string")
324
- return processTemplateParams(val, params);
325
- return val;
326
- });
327
- } catch {
328
- }
329
- }
330
- }
331
- ctx.tags = tags.filter((tag) => tag.tag !== "templateParams");
334
+ "init": function(_head) {
335
+ head = _head;
336
+ prevHash = head.resolvedOptions.document?.head.querySelector('meta[name="unhead:ssr"]')?.getAttribute("content") || false;
337
+ },
338
+ "tags:resolve": function({ tags }) {
339
+ const hash = shared.hashCode(
340
+ tags.filter((tag) => {
341
+ const entry = head.headEntries().find((e) => e._i === tag._e);
342
+ return entry && entry.mode !== "server";
343
+ }).map((tag) => shared.hashTag(tag)).join("")
344
+ );
345
+ if (prevHash !== hash && prevHash !== false)
346
+ dirty = true;
347
+ else
348
+ prevHash = hash;
349
+ },
350
+ "dom:beforeRender": function(ctx) {
351
+ ctx.shouldRender = dirty;
352
+ dirty = false;
353
+ },
354
+ "ssr:render": function({ tags }) {
355
+ tags.push({ tag: "meta", props: { name: "unhead:ssr", content: String(prevHash) } });
332
356
  }
333
357
  }
334
358
  });
@@ -372,30 +396,25 @@ function CapoPlugin(options) {
372
396
  });
373
397
  }
374
398
 
375
- const IsBrowser = typeof window !== "undefined";
399
+ const unheadComposablesImports = [
400
+ {
401
+ from: "unhead",
402
+ imports: shared.composableNames
403
+ }
404
+ ];
376
405
 
377
- exports.activeHead = void 0;
378
- function setActiveHead(head) {
379
- return exports.activeHead = head;
380
- }
381
406
  function getActiveHead() {
382
- return exports.activeHead;
407
+ return activeHead;
383
408
  }
384
409
 
385
410
  function useHead(input, options = {}) {
386
- const head = getActiveHead();
387
- if (head) {
388
- const isBrowser = IsBrowser || head.resolvedOptions?.document;
389
- if (options.mode === "server" && isBrowser || options.mode === "client" && !isBrowser)
390
- return;
391
- return head.push(input, options);
392
- }
411
+ return getActiveHead()?.push(input, options);
393
412
  }
394
413
 
395
414
  function useHeadSafe(input, options = {}) {
396
415
  return useHead(input, {
397
416
  ...options || {},
398
- transform: whitelistSafeInput
417
+ transform: shared.whitelistSafeInput
399
418
  });
400
419
  }
401
420
 
@@ -412,7 +431,7 @@ function useSeoMeta(input, options) {
412
431
  return useHead({
413
432
  title,
414
433
  titleTemplate,
415
- meta: unpackMeta(meta)
434
+ meta: shared.unpackMeta(meta)
416
435
  }, options);
417
436
  }
418
437
 
@@ -423,766 +442,17 @@ function useServerSeoMeta(input, options) {
423
442
  });
424
443
  }
425
444
 
426
- function useTagTitle(title) {
427
- return useHead({ title });
428
- }
429
- function useTagBase(base) {
430
- return useHead({ base });
431
- }
432
- function useTagMeta(meta) {
433
- return useHead({ meta: shared.asArray(meta) });
434
- }
435
- function useTagMetaFlat(meta) {
436
- return useTagMeta(unpackMeta(meta));
437
- }
438
- function useTagLink(link) {
439
- return useHead({ link: shared.asArray(link) });
440
- }
441
- function useTagScript(script) {
442
- return useHead({ script: shared.asArray(script) });
443
- }
444
- function useTagStyle(style) {
445
- return useHead({ style: shared.asArray(style) });
446
- }
447
- function useTagNoscript(noscript) {
448
- return useHead({ noscript: shared.asArray(noscript) });
449
- }
450
- function useHtmlAttrs(attrs) {
451
- return useHead({ htmlAttrs: attrs });
452
- }
453
- function useBodyAttrs(attrs) {
454
- return useHead({ bodyAttrs: attrs });
455
- }
456
- function useTitleTemplate(titleTemplate) {
457
- return useHead({ titleTemplate });
458
- }
459
- function useServerTagTitle(title) {
460
- return useServerHead({ title });
461
- }
462
- function useServerTagBase(base) {
463
- return useServerHead({ base });
464
- }
465
- function useServerTagMeta(meta) {
466
- return useServerHead({ meta: shared.asArray(meta) });
467
- }
468
- function useServerTagMetaFlat(meta) {
469
- return useServerTagMeta(unpackMeta(meta));
470
- }
471
- function useServerTagLink(link) {
472
- return useServerHead({ link: shared.asArray(link) });
473
- }
474
- function useServerTagScript(script) {
475
- return useServerHead({ script: shared.asArray(script) });
476
- }
477
- function useServerTagStyle(style) {
478
- return useServerHead({ style: shared.asArray(style) });
479
- }
480
- function useServerTagNoscript(noscript) {
481
- return useServerHead({ noscript: shared.asArray(noscript) });
482
- }
483
- function useServerHtmlAttrs(attrs) {
484
- return useServerHead({ htmlAttrs: attrs });
485
- }
486
- function useServerBodyAttrs(attrs) {
487
- return useServerHead({ bodyAttrs: attrs });
488
- }
489
- function useServerTitleTemplate(titleTemplate) {
490
- return useServerHead({ titleTemplate });
491
- }
492
-
493
- function asArray(input) {
494
- return Array.isArray(input) ? input : [input];
495
- }
496
- const InternalKeySymbol = "_$key";
497
- function packObject(input, options) {
498
- const keys = Object.keys(input);
499
- let [k, v] = keys;
500
- options = options || {};
501
- options.key = options.key || k;
502
- options.value = options.value || v;
503
- options.resolveKey = options.resolveKey || ((k2) => k2);
504
- const resolveKey = (index) => {
505
- const arr = asArray(options?.[index]);
506
- return arr.find((k2) => {
507
- if (typeof k2 === "string" && k2.includes(".")) {
508
- return k2;
509
- }
510
- return k2 && keys.includes(k2);
511
- });
512
- };
513
- const resolveValue = (k2, input2) => {
514
- if (k2.includes(".")) {
515
- const paths = k2.split(".");
516
- let val = input2;
517
- for (const path of paths)
518
- val = val[path];
519
- return val;
520
- }
521
- return input2[k2];
522
- };
523
- k = resolveKey("key") || k;
524
- v = resolveKey("value") || v;
525
- const dedupeKeyPrefix = input.key ? `${InternalKeySymbol}${input.key}-` : "";
526
- let keyValue = resolveValue(k, input);
527
- keyValue = options.resolveKey(keyValue);
528
- return {
529
- [`${dedupeKeyPrefix}${keyValue}`]: resolveValue(v, input)
530
- };
531
- }
532
-
533
- function packArray(input, options) {
534
- const packed = {};
535
- for (const i of input) {
536
- const packedObj = packObject(i, options);
537
- const pKey = Object.keys(packedObj)[0];
538
- const isDedupeKey = pKey.startsWith(InternalKeySymbol);
539
- if (!isDedupeKey && packed[pKey]) {
540
- packed[pKey] = Array.isArray(packed[pKey]) ? packed[pKey] : [packed[pKey]];
541
- packed[pKey].push(Object.values(packedObj)[0]);
542
- } else {
543
- packed[isDedupeKey ? pKey.split("-").slice(1).join("-") || pKey : pKey] = packedObj[pKey];
544
- }
545
- }
546
- return packed;
547
- }
548
-
549
- function unpackToArray(input, options) {
550
- const unpacked = [];
551
- const kFn = options.resolveKeyData || ((ctx) => ctx.key);
552
- const vFn = options.resolveValueData || ((ctx) => ctx.value);
553
- for (const [k, v] of Object.entries(input)) {
554
- unpacked.push(...(Array.isArray(v) ? v : [v]).map((i) => {
555
- const ctx = { key: k, value: i };
556
- const val = vFn(ctx);
557
- if (typeof val === "object")
558
- return unpackToArray(val, options);
559
- if (Array.isArray(val))
560
- return val;
561
- return {
562
- [typeof options.key === "function" ? options.key(ctx) : options.key]: kFn(ctx),
563
- [typeof options.value === "function" ? options.value(ctx) : options.value]: val
564
- };
565
- }).flat());
566
- }
567
- return unpacked;
568
- }
569
-
570
- function unpackToString(value, options) {
571
- return Object.entries(value).map(([key, value2]) => {
572
- if (typeof value2 === "object")
573
- value2 = unpackToString(value2, options);
574
- if (options.resolve) {
575
- const resolved = options.resolve({ key, value: value2 });
576
- if (resolved)
577
- return resolved;
578
- }
579
- if (typeof value2 === "number")
580
- value2 = value2.toString();
581
- if (typeof value2 === "string" && options.wrapValue) {
582
- value2 = value2.replace(new RegExp(options.wrapValue, "g"), `\\${options.wrapValue}`);
583
- value2 = `${options.wrapValue}${value2}${options.wrapValue}`;
584
- }
585
- return `${key}${options.keyValueSeparator || ""}${value2}`;
586
- }).join(options.entrySeparator || "");
587
- }
588
-
589
- const MetaPackingSchema = {
590
- robots: {
591
- unpack: {
592
- keyValueSeparator: ":"
593
- }
594
- },
595
- // Pragma directives
596
- contentSecurityPolicy: {
597
- unpack: {
598
- keyValueSeparator: " ",
599
- entrySeparator: "; "
600
- },
601
- metaKey: "http-equiv"
602
- },
603
- fbAppId: {
604
- keyValue: "fb:app_id",
605
- metaKey: "property"
606
- },
607
- ogSiteName: {
608
- keyValue: "og:site_name"
609
- },
610
- msapplicationTileImage: {
611
- keyValue: "msapplication-TileImage"
612
- },
613
- /**
614
- * Tile colour for windows
615
- */
616
- msapplicationTileColor: {
617
- keyValue: "msapplication-TileColor"
618
- },
619
- /**
620
- * URL of a config for windows tile.
621
- */
622
- msapplicationConfig: {
623
- keyValue: "msapplication-Config"
624
- },
625
- charset: {
626
- metaKey: "charset"
627
- },
628
- contentType: {
629
- metaKey: "http-equiv"
630
- },
631
- defaultStyle: {
632
- metaKey: "http-equiv"
633
- },
634
- xUaCompatible: {
635
- metaKey: "http-equiv"
636
- },
637
- refresh: {
638
- metaKey: "http-equiv"
639
- }
640
- };
641
-
642
- function packMeta(inputs) {
643
- const mappedPackingSchema = Object.entries(MetaPackingSchema).map(([key, value]) => [key, value.keyValue]);
644
- return packArray(inputs, {
645
- key: ["name", "property", "httpEquiv", "http-equiv", "charset"],
646
- value: ["content", "charset"],
647
- resolveKey(k) {
648
- let key = mappedPackingSchema.filter((sk) => sk[1] === k)?.[0]?.[0] || k;
649
- const replacer = (_, letter) => letter?.toUpperCase();
650
- key = key.replace(/:([a-z])/g, replacer).replace(/-([a-z])/g, replacer);
651
- return key;
652
- }
653
- });
654
- }
655
-
656
- const OpenGraphInputs = ["og:Image", "og:Video", "og:Audio", "twitter:Image"];
657
- const SimpleArrayUnpackMetas = ["themeColor"];
658
- const ColonPrefixKeys = /^(og|twitter|fb)/;
659
- const PropertyPrefixKeys = /^(og|fb)/;
660
- function resolveMetaKeyType(key) {
661
- return PropertyPrefixKeys.test(key) ? "property" : MetaPackingSchema[key]?.metaKey || "name";
662
- }
663
- function resolveMetaKeyValue(key) {
664
- return MetaPackingSchema[key]?.keyValue || fixKeyCase(key);
665
- }
666
- function fixKeyCase(key) {
667
- key = key.replace(/([A-Z])/g, "-$1").toLowerCase();
668
- if (ColonPrefixKeys.test(key)) {
669
- key = key.replace("secure-url", "secure_url").replace(/-/g, ":");
670
- }
671
- return key;
672
- }
673
- function changeKeyCasingDeep(input) {
674
- if (Array.isArray(input)) {
675
- return input.map((entry) => changeKeyCasingDeep(entry));
676
- }
677
- if (typeof input !== "object" || Array.isArray(input))
678
- return input;
679
- const output = {};
680
- for (const [key, value] of Object.entries(input))
681
- output[fixKeyCase(key)] = changeKeyCasingDeep(value);
682
- return output;
683
- }
684
- function unpackMeta(input) {
685
- const extras = [];
686
- OpenGraphInputs.forEach((key) => {
687
- const propKey = key.toLowerCase();
688
- const inputKey = `${key.replace(":", "")}`;
689
- const val = input[inputKey];
690
- if (typeof val === "object") {
691
- (Array.isArray(val) ? val : [val]).forEach((entry) => {
692
- if (!entry)
693
- return;
694
- const unpackedEntry = unpackToArray(entry, {
695
- key: key.startsWith("og") ? "property" : "name",
696
- value: "content",
697
- resolveKeyData({ key: key2 }) {
698
- return fixKeyCase(`${propKey}${key2 !== "url" ? `:${key2}` : ""}`);
699
- },
700
- resolveValueData({ value }) {
701
- return typeof value === "number" ? value.toString() : value;
702
- }
703
- });
704
- extras.push(
705
- ...unpackedEntry.sort((a, b) => a.property === propKey ? -1 : b.property === propKey ? 1 : 0)
706
- );
707
- });
708
- delete input[inputKey];
709
- }
710
- });
711
- SimpleArrayUnpackMetas.forEach((meta2) => {
712
- if (input[meta2] && typeof input[meta2] !== "string") {
713
- const val = Array.isArray(input[meta2]) ? input[meta2] : [input[meta2]];
714
- delete input[meta2];
715
- val.forEach((entry) => {
716
- extras.push({
717
- name: fixKeyCase(meta2),
718
- ...entry
719
- });
720
- });
721
- }
722
- });
723
- const meta = unpackToArray(input, {
724
- key({ key }) {
725
- return resolveMetaKeyType(key);
726
- },
727
- value({ key }) {
728
- return key === "charset" ? "charset" : "content";
729
- },
730
- resolveKeyData({ key }) {
731
- return resolveMetaKeyValue(key);
732
- },
733
- resolveValueData({ value, key }) {
734
- if (value === null)
735
- return "_null";
736
- if (typeof value === "object")
737
- return resolvePackedMetaObjectValue(value, key);
738
- return typeof value === "number" ? value.toString() : value;
739
- }
740
- });
741
- return [...extras, ...meta].filter((v) => typeof v.content === "undefined" || v.content !== "_null");
742
- }
743
- function resolvePackedMetaObjectValue(value, key) {
744
- const definition = MetaPackingSchema[key];
745
- if (key === "refresh")
746
- return `${value.seconds};url=${value.url}`;
747
- return unpackToString(
748
- changeKeyCasingDeep(value),
749
- {
750
- entrySeparator: ", ",
751
- keyValueSeparator: "=",
752
- resolve({ value: value2, key: key2 }) {
753
- if (value2 === null)
754
- return "";
755
- if (typeof value2 === "boolean")
756
- return `${key2}`;
757
- },
758
- ...definition?.unpack
759
- }
760
- );
761
- }
762
-
763
- async function normaliseTag(tagName, input, e) {
764
- const tag = { tag: tagName, props: {} };
765
- if (input instanceof Promise)
766
- input = await input;
767
- if (tagName === "templateParams") {
768
- tag.props = input;
769
- return tag;
770
- }
771
- if (["title", "titleTemplate"].includes(tagName)) {
772
- if (input && typeof input === "object") {
773
- tag.textContent = input.textContent;
774
- if (input.tagPriority)
775
- tag.tagPriority = input.tagPriority;
776
- } else {
777
- tag.textContent = input;
778
- }
779
- return tag;
780
- }
781
- if (typeof input === "string") {
782
- if (!["script", "noscript", "style"].includes(tagName))
783
- return false;
784
- if (tagName === "script" && (/^(https?:)?\/\//.test(input) || input.startsWith("/")))
785
- tag.props.src = input;
786
- else
787
- tag.innerHTML = input;
788
- return tag;
789
- }
790
- tag.props = await normaliseProps(tagName, { ...input });
791
- if (tag.props.children) {
792
- tag.props.innerHTML = tag.props.children;
793
- }
794
- delete tag.props.children;
795
- Object.keys(tag.props).filter((k) => shared.TagConfigKeys.includes(k)).forEach((k) => {
796
- if (!["innerHTML", "textContent"].includes(k) || shared.TagsWithInnerContent.includes(tag.tag)) {
797
- tag[k] = tag.props[k];
798
- }
799
- delete tag.props[k];
800
- });
801
- shared.TagConfigKeys.forEach((k) => {
802
- if (!tag[k] && e[k]) {
803
- tag[k] = e[k];
804
- }
805
- });
806
- ["innerHTML", "textContent"].forEach((k) => {
807
- if (tag.tag === "script" && typeof tag[k] === "string" && ["application/ld+json", "application/json"].includes(tag.props.type)) {
808
- try {
809
- tag[k] = JSON.parse(tag[k]);
810
- } catch (e2) {
811
- tag[k] = "";
812
- }
813
- }
814
- if (typeof tag[k] === "object")
815
- tag[k] = JSON.stringify(tag[k]);
816
- });
817
- if (tag.props.class)
818
- tag.props.class = normaliseClassProp(tag.props.class);
819
- if (tag.props.content && Array.isArray(tag.props.content))
820
- return tag.props.content.map((v) => ({ ...tag, props: { ...tag.props, content: v } }));
821
- return tag;
822
- }
823
- function normaliseClassProp(v) {
824
- if (typeof v === "object" && !Array.isArray(v)) {
825
- v = Object.keys(v).filter((k) => v[k]);
826
- }
827
- return (Array.isArray(v) ? v.join(" ") : v).split(" ").filter((c) => c.trim()).filter(Boolean).join(" ");
828
- }
829
- async function normaliseProps(tagName, props) {
830
- for (const k of Object.keys(props)) {
831
- const isDataKey = k.startsWith("data-");
832
- if (props[k] instanceof Promise) {
833
- props[k] = await props[k];
834
- }
835
- if (String(props[k]) === "true") {
836
- props[k] = isDataKey ? "true" : "";
837
- } else if (String(props[k]) === "false") {
838
- if (isDataKey) {
839
- props[k] = "false";
840
- } else {
841
- delete props[k];
842
- }
843
- }
844
- }
845
- return props;
846
- }
847
- const TagEntityBits = 10;
848
- async function normaliseEntryTags(e) {
849
- const tagPromises = [];
850
- Object.entries(e.resolvedInput).filter(([k, v]) => typeof v !== "undefined" && shared.ValidHeadTags.includes(k)).forEach(([k, value]) => {
851
- const v = shared.asArray(value);
852
- tagPromises.push(...v.map((props) => normaliseTag(k, props, e)).flat());
853
- });
854
- return (await Promise.all(tagPromises)).flat().filter(Boolean).map((t, i) => {
855
- t._e = e._i;
856
- t._p = (e._i << TagEntityBits) + i;
857
- return t;
858
- });
859
- }
860
-
861
- const WhitelistAttributes = {
862
- htmlAttrs: ["id", "class", "lang", "dir"],
863
- bodyAttrs: ["id", "class"],
864
- meta: ["id", "name", "property", "charset", "content"],
865
- noscript: ["id", "textContent"],
866
- script: ["id", "type", "textContent"],
867
- link: ["id", "color", "crossorigin", "fetchpriority", "href", "hreflang", "imagesrcset", "imagesizes", "integrity", "media", "referrerpolicy", "rel", "sizes", "type"]
868
- };
869
- function whitelistSafeInput(input) {
870
- const filtered = {};
871
- Object.keys(input).forEach((key) => {
872
- const tagValue = input[key];
873
- if (!tagValue)
874
- return;
875
- switch (key) {
876
- case "title":
877
- case "titleTemplate":
878
- case "templateParams":
879
- filtered[key] = tagValue;
880
- break;
881
- case "htmlAttrs":
882
- case "bodyAttrs":
883
- filtered[key] = {};
884
- WhitelistAttributes[key].forEach((a) => {
885
- if (tagValue[a])
886
- filtered[key][a] = tagValue[a];
887
- });
888
- Object.keys(tagValue || {}).filter((a) => a.startsWith("data-")).forEach((a) => {
889
- filtered[key][a] = tagValue[a];
890
- });
891
- break;
892
- case "meta":
893
- if (Array.isArray(tagValue)) {
894
- filtered[key] = tagValue.map((meta) => {
895
- const safeMeta = {};
896
- WhitelistAttributes.meta.forEach((key2) => {
897
- if (meta[key2] || key2.startsWith("data-"))
898
- safeMeta[key2] = meta[key2];
899
- });
900
- return safeMeta;
901
- }).filter((meta) => Object.keys(meta).length > 0);
902
- }
903
- break;
904
- case "link":
905
- if (Array.isArray(tagValue)) {
906
- filtered[key] = tagValue.map((meta) => {
907
- const link = {};
908
- WhitelistAttributes.link.forEach((key2) => {
909
- const val = meta[key2];
910
- if (key2 === "rel" && ["stylesheet", "canonical", "modulepreload", "prerender", "preload", "prefetch"].includes(val))
911
- return;
912
- if (key2 === "href") {
913
- if (val.includes("javascript:") || val.includes("data:"))
914
- return;
915
- link[key2] = val;
916
- } else if (val || key2.startsWith("data-")) {
917
- link[key2] = val;
918
- }
919
- });
920
- return link;
921
- }).filter((link) => Object.keys(link).length > 1 && !!link.rel);
922
- }
923
- break;
924
- case "noscript":
925
- if (Array.isArray(tagValue)) {
926
- filtered[key] = tagValue.map((meta) => {
927
- const noscript = {};
928
- WhitelistAttributes.noscript.forEach((key2) => {
929
- if (meta[key2] || key2.startsWith("data-"))
930
- noscript[key2] = meta[key2];
931
- });
932
- return noscript;
933
- }).filter((meta) => Object.keys(meta).length > 0);
934
- }
935
- break;
936
- case "script":
937
- if (Array.isArray(tagValue)) {
938
- filtered[key] = tagValue.map((script) => {
939
- const safeScript = {};
940
- WhitelistAttributes.script.forEach((s) => {
941
- if (script[s] || s.startsWith("data-")) {
942
- if (s === "textContent") {
943
- try {
944
- const jsonVal = typeof script[s] === "string" ? JSON.parse(script[s]) : script[s];
945
- safeScript[s] = JSON.stringify(jsonVal, null, 0);
946
- } catch (e) {
947
- }
948
- } else {
949
- safeScript[s] = script[s];
950
- }
951
- }
952
- });
953
- return safeScript;
954
- }).filter((meta) => Object.keys(meta).length > 0);
955
- }
956
- break;
957
- }
958
- });
959
- return filtered;
960
- }
961
-
962
- function CorePlugins() {
963
- return [
964
- // dedupe needs to come first
965
- DedupesTagsPlugin(),
966
- SortTagsPlugin(),
967
- TemplateParamsPlugin(),
968
- TitleTemplatePlugin(),
969
- ProvideTagHashPlugin(),
970
- EventHandlersPlugin(),
971
- DeprecatedTagAttrPlugin()
972
- ];
973
- }
974
- function DOMPlugins(options = {}) {
975
- return [
976
- dom.PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn })
977
- ];
978
- }
979
- function createHead(options = {}) {
980
- const head = createHeadCore({
981
- ...options,
982
- plugins: [...DOMPlugins(options), ...options?.plugins || []]
983
- });
984
- if (options.experimentalHashHydration && head.resolvedOptions.document)
985
- head._hash = dom.maybeGetSSRHash(head.resolvedOptions.document);
986
- setActiveHead(head);
987
- return head;
988
- }
989
- function createServerHead(options = {}) {
990
- const head = createHeadCore({
991
- ...options,
992
- mode: "server"
993
- });
994
- setActiveHead(head);
995
- return head;
996
- }
997
- function createHeadCore(options = {}) {
998
- let entries = [];
999
- let _sde = {};
1000
- let _eid = 0;
1001
- const hooks = hookable.createHooks();
1002
- if (options?.hooks)
1003
- hooks.addHooks(options.hooks);
1004
- options.plugins = [
1005
- ...CorePlugins(),
1006
- ...options?.plugins || []
1007
- ];
1008
- options.plugins.forEach((p) => p.hooks && hooks.addHooks(p.hooks));
1009
- options.document = options.document || (IsBrowser ? document : void 0);
1010
- const updated = () => hooks.callHook("entries:updated", head);
1011
- const head = {
1012
- resolvedOptions: options,
1013
- headEntries() {
1014
- return entries;
1015
- },
1016
- get hooks() {
1017
- return hooks;
1018
- },
1019
- use(plugin) {
1020
- if (plugin.hooks)
1021
- hooks.addHooks(plugin.hooks);
1022
- },
1023
- push(input, entryOptions) {
1024
- const activeEntry = {
1025
- _i: _eid++,
1026
- input,
1027
- _sde: {},
1028
- ...entryOptions
1029
- };
1030
- const mode = activeEntry?.mode || options.mode;
1031
- if (mode)
1032
- activeEntry.mode = mode;
1033
- entries.push(activeEntry);
1034
- updated();
1035
- return {
1036
- dispose() {
1037
- entries = entries.filter((e) => {
1038
- if (e._i !== activeEntry._i)
1039
- return true;
1040
- _sde = { ..._sde, ...e._sde || {} };
1041
- e._sde = {};
1042
- updated();
1043
- return false;
1044
- });
1045
- },
1046
- // a patch is the same as creating a new entry, just a nice DX
1047
- patch(input2) {
1048
- entries = entries.map((e) => {
1049
- if (e._i === activeEntry._i) {
1050
- activeEntry.input = e.input = input2;
1051
- updated();
1052
- }
1053
- return e;
1054
- });
1055
- }
1056
- };
1057
- },
1058
- async resolveTags() {
1059
- const resolveCtx = { tags: [], entries: [...entries] };
1060
- await hooks.callHook("entries:resolve", resolveCtx);
1061
- for (const entry of resolveCtx.entries) {
1062
- const resolved = entry.resolvedInput || entry.input;
1063
- entry.resolvedInput = await (entry.transform ? entry.transform(resolved) : resolved);
1064
- if (entry.resolvedInput) {
1065
- for (const tag of await normaliseEntryTags(entry)) {
1066
- const tagCtx = { tag, entry, resolvedOptions: head.resolvedOptions };
1067
- await hooks.callHook("tag:normalise", tagCtx);
1068
- resolveCtx.tags.push(tagCtx.tag);
1069
- }
1070
- }
1071
- }
1072
- await hooks.callHook("tags:beforeResolve", resolveCtx);
1073
- await hooks.callHook("tags:resolve", resolveCtx);
1074
- return resolveCtx.tags;
1075
- },
1076
- _popSideEffectQueue() {
1077
- const sde = { ..._sde };
1078
- _sde = {};
1079
- return sde;
1080
- },
1081
- _elMap: {}
1082
- };
1083
- head.hooks.callHook("init", head);
1084
- return head;
1085
- }
1086
-
1087
- const coreComposableNames = [
1088
- "getActiveHead"
1089
- ];
1090
- const composableNames = [
1091
- "useHead",
1092
- "useSeoMeta",
1093
- "useHeadSafe",
1094
- "useServerHead",
1095
- "useServerSeoMeta",
1096
- "useServerHeadSafe",
1097
- // deprecated
1098
- "useTagTitle",
1099
- "useTagBase",
1100
- "useTagMeta",
1101
- "useTagMetaFlat",
1102
- "useTagLink",
1103
- "useTagScript",
1104
- "useTagStyle",
1105
- "useTagNoscript",
1106
- "useHtmlAttrs",
1107
- "useBodyAttrs",
1108
- "useTitleTemplate",
1109
- "useServerTagTitle",
1110
- "useServerTagBase",
1111
- "useServerTagMeta",
1112
- "useServerTagMetaFlat",
1113
- "useServerTagLink",
1114
- "useServerTagScript",
1115
- "useServerTagStyle",
1116
- "useServerTagNoscript",
1117
- "useServerHtmlAttrs",
1118
- "useServerBodyAttrs",
1119
- "useServerTitleTemplate"
1120
- ];
1121
- const unheadComposablesImports = [
1122
- {
1123
- from: "unhead",
1124
- imports: [...coreComposableNames, ...composableNames]
1125
- }
1126
- ];
1127
-
445
+ exports.composableNames = shared.composableNames;
1128
446
  exports.CapoPlugin = CapoPlugin;
1129
- exports.CorePlugins = CorePlugins;
1130
- exports.DOMPlugins = DOMPlugins;
1131
- exports.DedupesTagsPlugin = DedupesTagsPlugin;
1132
- exports.DeprecatedTagAttrPlugin = DeprecatedTagAttrPlugin;
1133
- exports.EventHandlersPlugin = EventHandlersPlugin;
1134
- exports.ProvideTagHashPlugin = ProvideTagHashPlugin;
1135
- exports.SortModifiers = SortModifiers;
1136
- exports.SortTagsPlugin = SortTagsPlugin;
1137
- exports.TAG_ALIASES = TAG_ALIASES;
1138
- exports.TAG_WEIGHTS = TAG_WEIGHTS;
1139
- exports.TagEntityBits = TagEntityBits;
1140
- exports.TemplateParamsPlugin = TemplateParamsPlugin;
1141
- exports.TitleTemplatePlugin = TitleTemplatePlugin;
1142
- exports.composableNames = composableNames;
447
+ exports.HashHydrationPlugin = HashHydrationPlugin;
1143
448
  exports.createHead = createHead;
1144
449
  exports.createHeadCore = createHeadCore;
1145
450
  exports.createServerHead = createServerHead;
1146
451
  exports.getActiveHead = getActiveHead;
1147
- exports.normaliseClassProp = normaliseClassProp;
1148
- exports.normaliseEntryTags = normaliseEntryTags;
1149
- exports.normaliseProps = normaliseProps;
1150
- exports.normaliseTag = normaliseTag;
1151
- exports.packMeta = packMeta;
1152
- exports.processTemplateParams = processTemplateParams;
1153
- exports.resolveMetaKeyType = resolveMetaKeyType;
1154
- exports.resolveMetaKeyValue = resolveMetaKeyValue;
1155
- exports.resolvePackedMetaObjectValue = resolvePackedMetaObjectValue;
1156
- exports.setActiveHead = setActiveHead;
1157
- exports.tagWeight = tagWeight;
1158
452
  exports.unheadComposablesImports = unheadComposablesImports;
1159
- exports.unpackMeta = unpackMeta;
1160
- exports.useBodyAttrs = useBodyAttrs;
1161
453
  exports.useHead = useHead;
1162
454
  exports.useHeadSafe = useHeadSafe;
1163
- exports.useHtmlAttrs = useHtmlAttrs;
1164
455
  exports.useSeoMeta = useSeoMeta;
1165
- exports.useServerBodyAttrs = useServerBodyAttrs;
1166
456
  exports.useServerHead = useServerHead;
1167
457
  exports.useServerHeadSafe = useServerHeadSafe;
1168
- exports.useServerHtmlAttrs = useServerHtmlAttrs;
1169
458
  exports.useServerSeoMeta = useServerSeoMeta;
1170
- exports.useServerTagBase = useServerTagBase;
1171
- exports.useServerTagLink = useServerTagLink;
1172
- exports.useServerTagMeta = useServerTagMeta;
1173
- exports.useServerTagMetaFlat = useServerTagMetaFlat;
1174
- exports.useServerTagNoscript = useServerTagNoscript;
1175
- exports.useServerTagScript = useServerTagScript;
1176
- exports.useServerTagStyle = useServerTagStyle;
1177
- exports.useServerTagTitle = useServerTagTitle;
1178
- exports.useServerTitleTemplate = useServerTitleTemplate;
1179
- exports.useTagBase = useTagBase;
1180
- exports.useTagLink = useTagLink;
1181
- exports.useTagMeta = useTagMeta;
1182
- exports.useTagMetaFlat = useTagMetaFlat;
1183
- exports.useTagNoscript = useTagNoscript;
1184
- exports.useTagScript = useTagScript;
1185
- exports.useTagStyle = useTagStyle;
1186
- exports.useTagTitle = useTagTitle;
1187
- exports.useTitleTemplate = useTitleTemplate;
1188
- exports.whitelistSafeInput = whitelistSafeInput;