unhead 1.9.16 → 1.10.0-beta.3

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,72 +4,80 @@ const hookable = require('hookable');
4
4
  const dom = require('@unhead/dom');
5
5
  const shared = require('@unhead/shared');
6
6
 
7
- const UsesMergeStrategy = ["templateParams", "htmlAttrs", "bodyAttrs"];
7
+ const UsesMergeStrategy = /* @__PURE__ */ new Set(["templateParams", "htmlAttrs", "bodyAttrs"]);
8
8
  const DedupePlugin = shared.defineHeadPlugin({
9
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
- });
10
+ "tag:normalise": ({ tag }) => {
11
+ if (tag.props.hid) {
12
+ tag.key = tag.props.hid;
13
+ delete tag.props.hid;
14
+ }
15
+ if (tag.props.vmid) {
16
+ tag.key = tag.props.vmid;
17
+ delete tag.props.vmid;
18
+ }
19
+ if (tag.props.key) {
20
+ tag.key = tag.props.key;
21
+ delete tag.props.key;
22
+ }
17
23
  const generatedKey = shared.tagDedupeKey(tag);
18
24
  const dedupe = generatedKey || (tag.key ? `${tag.tag}:${tag.key}` : false);
19
25
  if (dedupe)
20
26
  tag._d = dedupe;
21
27
  },
22
- "tags:resolve": function(ctx) {
23
- const deduping = {};
24
- ctx.tags.forEach((tag) => {
28
+ "tags:resolve": (ctx) => {
29
+ const deduping = /* @__PURE__ */ Object.create(null);
30
+ for (const tag of ctx.tags) {
25
31
  const dedupeKey = (tag.key ? `${tag.tag}:${tag.key}` : tag._d) || tag._p;
26
32
  const dupedTag = deduping[dedupeKey];
27
33
  if (dupedTag) {
28
34
  let strategy = tag?.tagDuplicateStrategy;
29
- if (!strategy && UsesMergeStrategy.includes(tag.tag))
35
+ if (!strategy && UsesMergeStrategy.has(tag.tag))
30
36
  strategy = "merge";
31
37
  if (strategy === "merge") {
32
38
  const oldProps = dupedTag.props;
33
- ["class", "style"].forEach((key) => {
34
- if (oldProps[key]) {
35
- if (tag.props[key]) {
36
- if (key === "style" && !oldProps[key].endsWith(";"))
37
- oldProps[key] += ";";
38
- tag.props[key] = `${oldProps[key]} ${tag.props[key]}`;
39
- } else {
40
- tag.props[key] = oldProps[key];
41
- }
39
+ if (oldProps.style && tag.props.style) {
40
+ if (oldProps.style[oldProps.style.length - 1] !== ";") {
41
+ oldProps.style += ";";
42
42
  }
43
- });
43
+ tag.props.style = `${oldProps.style} ${tag.props.style}`;
44
+ }
45
+ if (oldProps.class && tag.props.class) {
46
+ tag.props.class = `${oldProps.class} ${tag.props.class}`;
47
+ } else if (oldProps.class) {
48
+ tag.props.class = oldProps.class;
49
+ }
44
50
  deduping[dedupeKey].props = {
45
51
  ...oldProps,
46
52
  ...tag.props
47
53
  };
48
- return;
54
+ continue;
49
55
  } else if (tag._e === dupedTag._e) {
50
56
  dupedTag._duped = dupedTag._duped || [];
51
57
  tag._d = `${dupedTag._d}:${dupedTag._duped.length + 1}`;
52
58
  dupedTag._duped.push(tag);
53
- return;
59
+ continue;
54
60
  } else if (shared.tagWeight(tag) > shared.tagWeight(dupedTag)) {
55
- return;
61
+ continue;
56
62
  }
57
63
  }
58
- const propCount = Object.keys(tag.props).length + (tag.innerHTML ? 1 : 0) + (tag.textContent ? 1 : 0);
59
- if (shared.HasElementTags.includes(tag.tag) && propCount === 0) {
64
+ const hasProps = tag.innerHTML || tag.textContent || Object.keys(tag.props).length !== 0;
65
+ if (!hasProps && shared.HasElementTags.has(tag.tag)) {
60
66
  delete deduping[dedupeKey];
61
- return;
67
+ continue;
62
68
  }
63
69
  deduping[dedupeKey] = tag;
64
- });
70
+ }
65
71
  const newTags = [];
66
- Object.values(deduping).forEach((tag) => {
72
+ for (const key in deduping) {
73
+ const tag = deduping[key];
67
74
  const dupes = tag._duped;
68
- delete tag._duped;
69
75
  newTags.push(tag);
70
- if (dupes)
76
+ if (dupes) {
77
+ delete tag._duped;
71
78
  newTags.push(...dupes);
72
- });
79
+ }
80
+ }
73
81
  ctx.tags = newTags;
74
82
  ctx.tags = ctx.tags.filter((t) => !(t.tag === "meta" && (t.props.name || t.props.property) && !t.props.content));
75
83
  }
@@ -79,53 +87,84 @@ const DedupePlugin = shared.defineHeadPlugin({
79
87
  const PayloadPlugin = shared.defineHeadPlugin({
80
88
  mode: "server",
81
89
  hooks: {
82
- "tags:resolve": function(ctx) {
90
+ "tags:resolve": (ctx) => {
83
91
  const payload = {};
84
- ctx.tags.filter((tag) => ["titleTemplate", "templateParams", "title"].includes(tag.tag) && tag._m === "server").forEach((tag) => {
85
- payload[tag.tag] = tag.tag.startsWith("title") ? tag.textContent : tag.props;
86
- });
87
- Object.keys(payload).length && ctx.tags.push({
88
- tag: "script",
89
- innerHTML: JSON.stringify(payload),
90
- props: { id: "unhead:payload", type: "application/json" }
91
- });
92
+ let hasPayload = false;
93
+ for (const tag of ctx.tags) {
94
+ if (tag._m !== "server" || tag.tag !== "titleTemplate" && tag.tag !== "templateParams" && tag.tag !== "title") {
95
+ continue;
96
+ }
97
+ payload[tag.tag] = tag.tag === "title" || tag.tag === "titleTemplate" ? tag.textContent : tag.props;
98
+ hasPayload = true;
99
+ }
100
+ if (hasPayload) {
101
+ ctx.tags.push({
102
+ tag: "script",
103
+ innerHTML: JSON.stringify(payload),
104
+ props: { id: "unhead:payload", type: "application/json" }
105
+ });
106
+ }
92
107
  }
93
108
  }
94
109
  });
95
110
 
96
- const ValidEventTags = ["script", "link", "bodyAttrs"];
111
+ const ValidEventTags = /* @__PURE__ */ new Set(["script", "link", "bodyAttrs"]);
97
112
  const EventHandlersPlugin = shared.defineHeadPlugin((head) => ({
98
113
  hooks: {
99
- "tags:resolve": function(ctx) {
100
- for (const tag of ctx.tags.filter((t) => ValidEventTags.includes(t.tag))) {
101
- Object.entries(tag.props).forEach(([key, value]) => {
102
- if (key.startsWith("on") && typeof value === "function") {
103
- if (head.ssr && shared.NetworkEvents.includes(key))
104
- tag.props[key] = `this.dataset.${key}fired = true`;
105
- else
106
- delete tag.props[key];
107
- tag._eventHandlers = tag._eventHandlers || {};
108
- tag._eventHandlers[key] = value;
114
+ "tags:resolve": (ctx) => {
115
+ for (const tag of ctx.tags) {
116
+ if (!ValidEventTags.has(tag.tag)) {
117
+ continue;
118
+ }
119
+ const props = tag.props;
120
+ for (const key in props) {
121
+ if (key[0] !== "o" || key[1] !== "n") {
122
+ continue;
109
123
  }
110
- });
111
- if (head.ssr && tag._eventHandlers && (tag.props.src || tag.props.href))
124
+ if (!Object.prototype.hasOwnProperty.call(props, key)) {
125
+ continue;
126
+ }
127
+ const value = props[key];
128
+ if (typeof value !== "function") {
129
+ continue;
130
+ }
131
+ if (head.ssr && shared.NetworkEvents.has(key)) {
132
+ props[key] = `this.dataset.${key}fired = true`;
133
+ } else {
134
+ delete props[key];
135
+ }
136
+ tag._eventHandlers = tag._eventHandlers || {};
137
+ tag._eventHandlers[key] = value;
138
+ }
139
+ if (head.ssr && tag._eventHandlers && (tag.props.src || tag.props.href)) {
112
140
  tag.key = tag.key || shared.hashCode(tag.props.src || tag.props.href);
141
+ }
113
142
  }
114
143
  },
115
- "dom:renderTag": function({ $el, tag }) {
116
- for (const k of Object.keys($el?.dataset || {}).filter((k2) => shared.NetworkEvents.some((e) => `${e}fired` === k2))) {
117
- const ek = k.replace("fired", "");
118
- tag._eventHandlers?.[ek]?.call($el, new Event(ek.replace("on", "")));
144
+ "dom:renderTag": ({ $el, tag }) => {
145
+ const dataset = $el?.dataset;
146
+ if (!dataset) {
147
+ return;
148
+ }
149
+ for (const k in dataset) {
150
+ if (!k.endsWith("fired")) {
151
+ continue;
152
+ }
153
+ const ek = k.slice(0, -5);
154
+ if (!shared.NetworkEvents.has(ek)) {
155
+ continue;
156
+ }
157
+ tag._eventHandlers?.[ek]?.call($el, new Event(ek.substring(2)));
119
158
  }
120
159
  }
121
160
  }
122
161
  }));
123
162
 
124
- const DupeableTags = ["link", "style", "script", "noscript"];
163
+ const DupeableTags = /* @__PURE__ */ new Set(["link", "style", "script", "noscript"]);
125
164
  const HashKeyedPlugin = shared.defineHeadPlugin({
126
165
  hooks: {
127
166
  "tag:normalise": ({ tag }) => {
128
- if (tag.key && DupeableTags.includes(tag.tag)) {
167
+ if (tag.key && DupeableTags.has(tag.tag)) {
129
168
  tag.props["data-hid"] = tag._h = shared.hashCode(tag.key);
130
169
  }
131
170
  }
@@ -135,17 +174,32 @@ const HashKeyedPlugin = shared.defineHeadPlugin({
135
174
  const SortPlugin = shared.defineHeadPlugin({
136
175
  hooks: {
137
176
  "tags:resolve": (ctx) => {
138
- const tagPositionForKey = (key) => ctx.tags.find((tag) => tag._d === key)?._p;
139
- for (const { prefix, offset } of shared.SortModifiers) {
140
- for (const tag of ctx.tags.filter((tag2) => typeof tag2.tagPriority === "string" && tag2.tagPriority.startsWith(prefix))) {
141
- const position = tagPositionForKey(
142
- tag.tagPriority.replace(prefix, "")
143
- );
144
- if (typeof position !== "undefined")
177
+ for (const tag of ctx.tags) {
178
+ if (typeof tag.tagPriority !== "string") {
179
+ continue;
180
+ }
181
+ for (const { prefix, offset } of shared.SortModifiers) {
182
+ if (!tag.tagPriority.startsWith(prefix)) {
183
+ continue;
184
+ }
185
+ const key = tag.tagPriority.substring(prefix.length);
186
+ const position = ctx.tags.find((tag2) => tag2._d === key)?._p;
187
+ if (position !== void 0) {
145
188
  tag._p = position + offset;
189
+ break;
190
+ }
146
191
  }
147
192
  }
148
- ctx.tags.sort((a, b) => a._p - b._p).sort((a, b) => shared.tagWeight(a) - shared.tagWeight(b));
193
+ ctx.tags.sort((a, b) => {
194
+ const aWeight = shared.tagWeight(a);
195
+ const bWeight = shared.tagWeight(b);
196
+ if (aWeight < bWeight) {
197
+ return -1;
198
+ } else if (aWeight > bWeight) {
199
+ return 1;
200
+ }
201
+ return a._p - b._p;
202
+ });
149
203
  }
150
204
  }
151
205
  });
@@ -155,30 +209,45 @@ const SupportedAttrs = {
155
209
  link: "href",
156
210
  htmlAttrs: "lang"
157
211
  };
212
+ const contentAttrs = ["innerHTML", "textContent"];
158
213
  const TemplateParamsPlugin = shared.defineHeadPlugin((head) => ({
159
214
  hooks: {
160
215
  "tags:resolve": (ctx) => {
161
216
  const { tags } = ctx;
162
- const title = tags.find((tag) => tag.tag === "title")?.textContent;
163
- const idx = tags.findIndex((tag) => tag.tag === "templateParams");
164
- const params = idx !== -1 ? tags[idx].props : {};
217
+ let templateParams;
218
+ for (let i = 0; i < tags.length; i += 1) {
219
+ const tag = tags[i];
220
+ if (tag.tag !== "templateParams") {
221
+ continue;
222
+ }
223
+ templateParams = ctx.tags.splice(i, 1)[0].props;
224
+ i -= 1;
225
+ }
226
+ const params = templateParams || {};
165
227
  const sep = params.separator || "|";
166
228
  delete params.separator;
167
- params.pageTitle = shared.processTemplateParams(params.pageTitle || title || "", params, sep);
168
- for (const tag of tags.filter((t) => t.processTemplateParams !== false)) {
229
+ params.pageTitle = shared.processTemplateParams(
230
+ // find templateParams
231
+ params.pageTitle || tags.find((tag) => tag.tag === "title")?.textContent || "",
232
+ params,
233
+ sep
234
+ );
235
+ for (const tag of tags) {
236
+ if (tag.processTemplateParams === false) {
237
+ continue;
238
+ }
169
239
  const v = SupportedAttrs[tag.tag];
170
240
  if (v && typeof tag.props[v] === "string") {
171
241
  tag.props[v] = shared.processTemplateParams(tag.props[v], params, sep);
172
- } else if (tag.processTemplateParams === true || ["titleTemplate", "title"].includes(tag.tag)) {
173
- ["innerHTML", "textContent"].forEach((p) => {
242
+ } else if (tag.processTemplateParams || tag.tag === "titleTemplate" || tag.tag === "title") {
243
+ for (const p of contentAttrs) {
174
244
  if (typeof tag[p] === "string")
175
245
  tag[p] = shared.processTemplateParams(tag[p], params, sep);
176
- });
246
+ }
177
247
  }
178
248
  }
179
249
  head._templateParams = params;
180
250
  head._separator = sep;
181
- ctx.tags = tags.filter((tag) => tag.tag !== "templateParams");
182
251
  }
183
252
  }
184
253
  }));
@@ -187,42 +256,49 @@ const TitleTemplatePlugin = shared.defineHeadPlugin({
187
256
  hooks: {
188
257
  "tags:resolve": (ctx) => {
189
258
  const { tags } = ctx;
190
- let titleTemplateIdx = tags.findIndex((i) => i.tag === "titleTemplate");
191
- const titleIdx = tags.findIndex((i) => i.tag === "title");
192
- if (titleIdx !== -1 && titleTemplateIdx !== -1) {
259
+ let titleTag;
260
+ let titleTemplateTag;
261
+ for (let i = 0; i < tags.length; i += 1) {
262
+ const tag = tags[i];
263
+ if (tag.tag === "title") {
264
+ titleTag = tag;
265
+ } else if (tag.tag === "titleTemplate") {
266
+ titleTemplateTag = tag;
267
+ }
268
+ }
269
+ if (titleTemplateTag && titleTag) {
193
270
  const newTitle = shared.resolveTitleTemplate(
194
- tags[titleTemplateIdx].textContent,
195
- tags[titleIdx].textContent
271
+ titleTemplateTag.textContent,
272
+ titleTag.textContent
196
273
  );
197
274
  if (newTitle !== null) {
198
- tags[titleIdx].textContent = newTitle || tags[titleIdx].textContent;
275
+ titleTag.textContent = newTitle || titleTag.textContent;
199
276
  } else {
200
- delete tags[titleIdx];
277
+ ctx.tags.splice(ctx.tags.indexOf(titleTag), 1);
201
278
  }
202
- } else if (titleTemplateIdx !== -1) {
279
+ } else if (titleTemplateTag) {
203
280
  const newTitle = shared.resolveTitleTemplate(
204
- tags[titleTemplateIdx].textContent
281
+ titleTemplateTag.textContent
205
282
  );
206
283
  if (newTitle !== null) {
207
- tags[titleTemplateIdx].textContent = newTitle;
208
- tags[titleTemplateIdx].tag = "title";
209
- titleTemplateIdx = -1;
284
+ titleTemplateTag.textContent = newTitle;
285
+ titleTemplateTag.tag = "title";
286
+ titleTemplateTag = void 0;
210
287
  }
211
288
  }
212
- if (titleTemplateIdx !== -1) {
213
- delete tags[titleTemplateIdx];
289
+ if (titleTemplateTag) {
290
+ ctx.tags.splice(ctx.tags.indexOf(titleTemplateTag), 1);
214
291
  }
215
- ctx.tags = tags.filter(Boolean);
216
292
  }
217
293
  }
218
294
  });
219
295
 
220
296
  const XSSPlugin = shared.defineHeadPlugin({
221
297
  hooks: {
222
- "tags:afterResolve": function(ctx) {
298
+ "tags:afterResolve": (ctx) => {
223
299
  for (const tag of ctx.tags) {
224
300
  if (typeof tag.innerHTML === "string") {
225
- if (tag.innerHTML && ["application/ld+json", "application/json"].includes(tag.props.type)) {
301
+ if (tag.innerHTML && (tag.props.type === "application/ld+json" || tag.props.type === "application/json")) {
226
302
  tag.innerHTML = tag.innerHTML.replace(/</g, "\\u003C");
227
303
  } else {
228
304
  tag.innerHTML = tag.innerHTML.replace(new RegExp(`</${tag.tag}`, "g"), `<\\/${tag.tag}`);
@@ -293,12 +369,11 @@ function createHeadCore(options = {}) {
293
369
  },
294
370
  // a patch is the same as creating a new entry, just a nice DX
295
371
  patch(input2) {
296
- entries = entries.map((e) => {
372
+ for (const e of entries) {
297
373
  if (e._i === entry._i) {
298
374
  e.input = entry.input = input2;
299
375
  }
300
- return e;
301
- });
376
+ }
302
377
  updated();
303
378
  }
304
379
  };
@@ -349,7 +424,7 @@ const importRe = /@import/;
349
424
  function CapoPlugin(options) {
350
425
  return shared.defineHeadPlugin({
351
426
  hooks: {
352
- "tags:beforeResolve": function({ tags }) {
427
+ "tags:beforeResolve": ({ tags }) => {
353
428
  for (const tag of tags) {
354
429
  if (tag.tagPosition && tag.tagPosition !== "head")
355
430
  continue;
@@ -367,11 +442,11 @@ function CapoPlugin(options) {
367
442
  tag.tagPriority = 50;
368
443
  } else if (isLink && tag.props.rel === "stylesheet" || tag.tag === "style") {
369
444
  tag.tagPriority = 60;
370
- } else if (isLink && ["preload", "modulepreload"].includes(tag.props.rel)) {
445
+ } else if (isLink && (tag.props.rel === "preload" || tag.props.rel === "modulepreload")) {
371
446
  tag.tagPriority = 70;
372
447
  } else if (isScript && isTruthy(tag.props.defer) && tag.props.src && !isTruthy(tag.props.async)) {
373
448
  tag.tagPriority = 80;
374
- } else if (isLink && ["prefetch", "dns-prefetch", "prerender"].includes(tag.props.rel)) {
449
+ } else if (isLink && (tag.props.rel === "prefetch" || tag.props.rel === "dns-prefetch" || tag.props.rel === "prerender")) {
375
450
  tag.tagPriority = 90;
376
451
  }
377
452
  }
@@ -402,9 +477,9 @@ function useHead(input, options = {}) {
402
477
  return head?.push(input, options);
403
478
  }
404
479
 
405
- function useHeadSafe(input, options = {}) {
480
+ function useHeadSafe(input, options) {
406
481
  return useHead(input, {
407
- ...options || {},
482
+ ...options,
408
483
  transform: shared.whitelistSafeInput
409
484
  });
410
485
  }
@@ -441,18 +516,25 @@ function useSeoMeta(input, options) {
441
516
 
442
517
  function useServerSeoMeta(input, options) {
443
518
  return useSeoMeta(input, {
444
- ...options || {},
519
+ ...options,
445
520
  mode: "server"
446
521
  });
447
522
  }
448
523
 
524
+ const ScriptProxyTarget = Symbol("ScriptProxyTarget");
525
+ function sharedTarget() {
526
+ }
527
+ sharedTarget[ScriptProxyTarget] = true;
528
+ function resolveScriptKey(input) {
529
+ return input.key || shared.hashCode(input.src || (typeof input.innerHTML === "string" ? input.innerHTML : ""));
530
+ }
449
531
  function useScript(_input, _options) {
450
532
  const input = typeof _input === "string" ? { src: _input } : _input;
451
533
  const options = _options || {};
452
534
  const head = options.head || getActiveHead();
453
535
  if (!head)
454
536
  throw new Error("Missing Unhead context.");
455
- const id = input.key || shared.hashCode(input.src || (typeof input.innerHTML === "string" ? input.innerHTML : ""));
537
+ const id = resolveScriptKey(input);
456
538
  if (head._scripts?.[id])
457
539
  return head._scripts[id];
458
540
  options.beforeInit?.();
@@ -460,7 +542,7 @@ function useScript(_input, _options) {
460
542
  script.status = s;
461
543
  head.hooks.callHook(`script:updated`, hookCtx);
462
544
  };
463
- const trigger = typeof options.trigger !== "undefined" ? options.trigger : "client";
545
+ const trigger = options.trigger !== void 0 ? options.trigger : "client";
464
546
  shared.ScriptNetworkEvents.forEach((fn) => {
465
547
  const _fn = typeof input[fn] === "function" ? input[fn].bind(options.eventContext) : null;
466
548
  input[fn] = (e) => {
@@ -468,15 +550,23 @@ function useScript(_input, _options) {
468
550
  _fn?.(e);
469
551
  };
470
552
  });
471
- const proxy = { instance: !head.ssr && options?.use?.() || {} };
553
+ const _cbs = { loaded: [], error: [] };
554
+ const _registerCb = (key, cb) => {
555
+ const i = _cbs[key].push(cb);
556
+ return () => _cbs[key].splice(i - 1, 1);
557
+ };
472
558
  const loadPromise = new Promise((resolve, reject) => {
559
+ if (head.ssr)
560
+ return;
473
561
  const emit = (api) => requestAnimationFrame(() => resolve(api));
474
562
  const _ = head.hooks.hook("script:updated", ({ script: script2 }) => {
475
563
  if (script2.id === id && (script2.status === "loaded" || script2.status === "error")) {
476
564
  if (script2.status === "loaded") {
477
565
  if (typeof options.use === "function") {
478
566
  const api = options.use();
479
- api && emit(api);
567
+ if (api) {
568
+ emit(api);
569
+ }
480
570
  } else {
481
571
  emit({});
482
572
  }
@@ -486,8 +576,10 @@ function useScript(_input, _options) {
486
576
  _();
487
577
  }
488
578
  });
489
- }).then((api) => proxy.instance = api);
490
- const script = {
579
+ });
580
+ const script = Object.assign(loadPromise, {
581
+ instance: !head.ssr && options?.use?.() || null,
582
+ proxy: null,
491
583
  id,
492
584
  status: "awaitingLoad",
493
585
  remove() {
@@ -499,7 +591,7 @@ function useScript(_input, _options) {
499
591
  }
500
592
  return false;
501
593
  },
502
- load() {
594
+ load(cb) {
503
595
  if (!script.entry) {
504
596
  syncStatus("loading");
505
597
  const defaults = {
@@ -514,9 +606,26 @@ function useScript(_input, _options) {
514
606
  script: [{ ...defaults, ...input, key: `script.${id}` }]
515
607
  }, options);
516
608
  }
609
+ if (cb)
610
+ _registerCb("loaded", cb);
517
611
  return loadPromise;
518
- }
519
- };
612
+ },
613
+ onLoaded(cb) {
614
+ return _registerCb("loaded", cb);
615
+ },
616
+ onError(cb) {
617
+ return _registerCb("error", cb);
618
+ },
619
+ _cbs
620
+ });
621
+ loadPromise.then((api) => {
622
+ script.instance = api;
623
+ _cbs.loaded.forEach((cb) => cb(api));
624
+ _cbs.loaded = [];
625
+ }).catch((err) => {
626
+ _cbs.error.forEach((cb) => cb(err));
627
+ _cbs.error = [];
628
+ });
520
629
  const hookCtx = { script };
521
630
  if (trigger === "client" && !head.ssr || trigger === "server" && head.ssr)
522
631
  script.load();
@@ -524,27 +633,53 @@ function useScript(_input, _options) {
524
633
  trigger.then(script.load);
525
634
  else if (typeof trigger === "function")
526
635
  trigger(async () => script.load());
527
- proxy.$script = Object.assign(loadPromise, script);
528
- const instance = new Proxy(proxy, {
529
- get({ instance: _ }, k) {
530
- const stub = options.stub?.({ script: proxy.$script, fn: k });
531
- if (stub)
532
- return stub;
533
- if (k === "$script")
534
- return proxy.$script;
535
- const exists = _ && k in _ && typeof _[k] !== "undefined";
536
- head.hooks.callHook("script:instance-fn", { script, fn: k, exists });
537
- return exists ? Reflect.get(_, k) : (...args) => loadPromise.then((api) => {
538
- const _k = Reflect.get(api, k);
539
- return typeof _k === "function" ? Reflect.apply(api[k], api, args) : _k;
540
- });
636
+ script.$script = script;
637
+ const proxyChain = (instance, accessor, accessors) => {
638
+ return new Proxy((!accessor ? instance : instance?.[accessor]) || sharedTarget, {
639
+ get(_, k, r) {
640
+ head.hooks.callHook("script:instance-fn", { script, fn: k, exists: k in _ });
641
+ if (!accessor) {
642
+ const stub = options.stub?.({ script, fn: k });
643
+ if (stub)
644
+ return stub;
645
+ }
646
+ if (_ && k in _) {
647
+ return Reflect.get(_, k, r);
648
+ }
649
+ if (k === Symbol.iterator) {
650
+ return [][Symbol.iterator];
651
+ }
652
+ return proxyChain(accessor ? instance?.[accessor] : instance, k, accessors || [k]);
653
+ },
654
+ async apply(_, _this, args) {
655
+ if (head.ssr && _[ScriptProxyTarget])
656
+ return;
657
+ let instance2;
658
+ const access = (fn2) => {
659
+ instance2 = fn2 || instance2;
660
+ for (let i = 0; i < (accessors || []).length; i++) {
661
+ const k = (accessors || [])[i];
662
+ fn2 = fn2?.[k];
663
+ }
664
+ return fn2;
665
+ };
666
+ const fn = access(script.instance) || access(await loadPromise);
667
+ return typeof fn === "function" ? Reflect.apply(fn, instance2, args) : fn;
668
+ }
669
+ });
670
+ };
671
+ script.proxy = proxyChain(script.instance);
672
+ const res = new Proxy(script, {
673
+ get(_, k) {
674
+ const target = k in script ? script : script.proxy;
675
+ if (k === "then" || k === "catch") {
676
+ return script[k].bind(script);
677
+ }
678
+ return Reflect.get(target, k, target);
541
679
  }
542
680
  });
543
- head._scripts = Object.assign(
544
- head._scripts || {},
545
- { [id]: instance }
546
- );
547
- return instance;
681
+ head._scripts = Object.assign(head._scripts || {}, { [id]: res });
682
+ return res;
548
683
  }
549
684
 
550
685
  exports.composableNames = shared.composableNames;
@@ -554,6 +689,7 @@ exports.createHead = createHead;
554
689
  exports.createHeadCore = createHeadCore;
555
690
  exports.createServerHead = createServerHead;
556
691
  exports.getActiveHead = getActiveHead;
692
+ exports.resolveScriptKey = resolveScriptKey;
557
693
  exports.unheadComposablesImports = unheadComposablesImports;
558
694
  exports.useHead = useHead;
559
695
  exports.useHeadSafe = useHeadSafe;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _unhead_schema from '@unhead/schema';
2
- import { Head, CreateHeadOptions, Unhead, MergeHead, HeadEntryOptions, ActiveHeadEntry, HeadSafe, UseSeoMetaInput, UseScriptInput, UseScriptOptions, ScriptInstance } from '@unhead/schema';
2
+ import { Head, CreateHeadOptions, Unhead, MergeHead, HeadEntryOptions, ActiveHeadEntry, HeadSafe, UseSeoMetaInput, ScriptInstance, AsAsyncFunctionValues, UseScriptResolvedInput, UseScriptInput, UseScriptOptions, UseFunctionType } from '@unhead/schema';
3
3
  export { composableNames } from '@unhead/shared';
4
4
 
5
5
  declare function createHead<T extends {} = Head>(options?: CreateHeadOptions): Unhead<T>;
@@ -41,14 +41,18 @@ declare function useSeoMeta(input: UseSeoMetaInput, options?: HeadEntryOptions):
41
41
 
42
42
  declare function useServerSeoMeta(input: UseSeoMetaInput, options?: HeadEntryOptions): ActiveHeadEntry<any> | void;
43
43
 
44
+ type UseScriptContext<T extends Record<symbol | string, any>> = (Promise<T> & ScriptInstance<T>) & AsAsyncFunctionValues<T> & {
45
+ /**
46
+ * @deprecated Use top-level functions instead.
47
+ */
48
+ $script: Promise<T> & ScriptInstance<T>;
49
+ };
50
+ declare function resolveScriptKey(input: UseScriptResolvedInput): any;
44
51
  /**
45
52
  * Load third-party scripts with SSR support and a proxied API.
46
53
  *
47
- * @experimental
48
54
  * @see https://unhead.unjs.io/usage/composables/use-script
49
55
  */
50
- declare function useScript<T extends Record<symbol | string, any>>(_input: UseScriptInput, _options?: UseScriptOptions<T>): T & {
51
- $script: Promise<T> & ScriptInstance<T>;
52
- };
56
+ declare function useScript<T extends Record<symbol | string, any> = Record<symbol | string, any>, U = Record<symbol | string, any>>(_input: UseScriptInput, _options?: UseScriptOptions<T, U>): UseScriptContext<UseFunctionType<UseScriptOptions<T, U>, T>>;
53
57
 
54
- export { CapoPlugin, HashHydrationPlugin, type UseHeadInput, createHead, createHeadCore, createServerHead, getActiveHead, unheadComposablesImports, useHead, useHeadSafe, useScript, useSeoMeta, useServerHead, useServerHeadSafe, useServerSeoMeta };
58
+ export { CapoPlugin, HashHydrationPlugin, type UseHeadInput, type UseScriptContext, createHead, createHeadCore, createServerHead, getActiveHead, resolveScriptKey, unheadComposablesImports, useHead, useHeadSafe, useScript, useSeoMeta, useServerHead, useServerHeadSafe, useServerSeoMeta };