unhead 1.8.19 → 1.9.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.
Files changed (3) hide show
  1. package/dist/index.cjs +42 -105
  2. package/dist/index.mjs +43 -106
  3. package/package.json +4 -4
package/dist/index.cjs CHANGED
@@ -457,48 +457,16 @@ function useServerSeoMeta(input, options) {
457
457
  });
458
458
  }
459
459
 
460
- const UseScriptDefaults = {
461
- defer: true,
462
- fetchpriority: "low"
463
- };
464
- const requestIdleCallback = typeof window === "undefined" ? () => {
465
- } : globalThis.requestIdleCallback || ((cb) => {
466
- const start = Date.now();
467
- const idleDeadline = {
468
- didTimeout: false,
469
- timeRemaining: () => Math.max(0, 50 - (Date.now() - start))
470
- };
471
- return setTimeout(() => {
472
- cb(idleDeadline);
473
- }, 1);
474
- });
475
460
  function useScript(_input, _options) {
476
461
  const input = typeof _input === "string" ? { src: _input } : _input;
477
462
  const options = _options || {};
478
463
  const head = options.head || getActiveHead();
479
464
  if (!head)
480
- throw new Error("No active head found, please provide a head instance or use the useHead composable");
465
+ throw new Error("Missing Unhead context.");
481
466
  const id = input.key || shared.hashCode(input.src || (typeof input.innerHTML === "string" ? input.innerHTML : ""));
482
467
  const key = `use-script.${id}`;
483
468
  if (head._scripts?.[id])
484
469
  return head._scripts[id];
485
- async function transform(entry) {
486
- const script2 = await (options.transform || ((input2) => input2))(entry.script[0]);
487
- const ctx = { script: script2 };
488
- await head.hooks.callHook("script:transform", ctx);
489
- return { script: [ctx.script] };
490
- }
491
- function maybeHintEarlyConnection(rel) {
492
- if (
493
- // opt-out
494
- options.skipEarlyConnections || !input.src.includes("//") || !head.ssr
495
- )
496
- return;
497
- const key2 = `use-script.${id}.early-connection`;
498
- head.push({
499
- link: [{ key: key2, rel, href: new URL(input.src).origin }]
500
- }, { mode: "server" });
501
- }
502
470
  const script = {
503
471
  id,
504
472
  status: "awaitingLoad",
@@ -513,109 +481,78 @@ function useScript(_input, _options) {
513
481
  }
514
482
  return false;
515
483
  },
516
- waitForLoad() {
517
- return new Promise((resolve) => {
518
- if (script.status === "loaded")
519
- resolve(options.use?.());
520
- function watchForScriptLoaded({ script: script2 }) {
521
- if (script2.id === id && script2.status === "loaded") {
522
- script2.loaded = true;
523
- resolve(options.use?.());
524
- head.hooks.removeHook("script:updated", watchForScriptLoaded);
525
- }
526
- }
527
- head.hooks.hook("script:updated", watchForScriptLoaded);
528
- });
529
- },
530
484
  load() {
531
485
  if (script.status !== "awaitingLoad")
532
- return script.waitForLoad();
486
+ return script.loadPromise;
533
487
  script.status = "loading";
534
488
  head.hooks.callHook(`script:updated`, hookCtx);
535
489
  script.entry = head.push({
536
490
  script: [
537
- // async by default
538
- { ...UseScriptDefaults, ...input, key }
491
+ { defer: true, fetchpriority: "low", ...input, key }
539
492
  ]
540
- }, {
541
- ...options,
542
- // @ts-expect-error untyped
543
- transform,
544
- head
545
- });
546
- return script.waitForLoad();
493
+ }, options);
494
+ return script.loadPromise;
547
495
  }
548
496
  };
497
+ script.loadPromise = new Promise((resolve, reject) => {
498
+ const removeHook2 = head.hooks.hook("script:updated", ({ script: script2 }) => {
499
+ if (script2.id === id && (script2.status === "loaded" || script2.status === "error")) {
500
+ script2.status === "loaded" && resolve(options.use?.());
501
+ script2.status === "error" && reject(new Error(`Failed to load script: ${input.src}`));
502
+ removeHook2();
503
+ }
504
+ });
505
+ });
549
506
  const hookCtx = { script };
550
- shared.NetworkEvents.forEach((fn) => {
551
- const _fn = typeof input[fn] === "function" ? input[fn].bind({}) : null;
507
+ shared.ScriptNetworkEvents.forEach((fn) => {
552
508
  input[fn] = (e) => {
553
509
  script.status = fn === "onload" ? "loaded" : fn === "onerror" ? "error" : "loading";
554
510
  head.hooks.callHook(`script:updated`, hookCtx);
555
- _fn && _fn(e);
511
+ typeof input[fn] === "function" && input[fn].call(options.eventContext, e);
556
512
  };
557
513
  });
558
- let trigger = options.trigger;
559
- if (trigger) {
560
- const isIdle = trigger === "idle";
561
- if (isIdle) {
562
- if (head.ssr)
563
- trigger = "manual";
564
- else
565
- trigger = new Promise((resolve) => requestIdleCallback(() => resolve()));
566
- }
567
- trigger === "manual" && (trigger = new Promise(() => {
568
- }));
514
+ const trigger = options.trigger;
515
+ if (options.trigger)
569
516
  trigger instanceof Promise && trigger.then(script.load);
570
- maybeHintEarlyConnection(isIdle ? "preconnect" : "dns-prefetch");
571
- } else {
517
+ else
572
518
  script.load();
573
- maybeHintEarlyConnection("preconnect");
574
- }
575
- function resolveInnerHtmlLoad(ctx) {
576
- if (ctx.tag.key === key) {
577
- if (ctx.tag.innerHTML) {
578
- setTimeout(
579
- () => {
580
- script.status = "loaded";
581
- head.hooks.callHook("script:updated", hookCtx);
582
- typeof input.onload === "function" && input.onload(new Event("load"));
583
- },
584
- 5
585
- /* give inline script a chance to run */
586
- );
587
- }
588
- head.hooks.removeHook("dom:renderTag", resolveInnerHtmlLoad);
519
+ const removeHook = head.hooks.hook("dom:renderTag", (ctx) => {
520
+ if (ctx.tag.key !== key)
521
+ return;
522
+ if (ctx.tag.innerHTML) {
523
+ setTimeout(
524
+ () => {
525
+ script.status = "loaded";
526
+ head.hooks.callHook("script:updated", hookCtx);
527
+ typeof input.onload === "function" && input.onload.call(options.eventContext, new Event("load"));
528
+ },
529
+ 5
530
+ /* give inline script a chance to run */
531
+ );
589
532
  }
590
- }
591
- head.hooks.hook("dom:renderTag", resolveInnerHtmlLoad);
533
+ removeHook();
534
+ });
592
535
  const instance = new Proxy({}, {
593
536
  get(_, fn) {
594
- const stub = options.stub?.({ script, fn });
537
+ const $script = Object.assign(script.loadPromise, script);
538
+ const stub = options.stub?.({ script: $script, fn });
595
539
  if (stub)
596
540
  return stub;
597
541
  if (fn === "$script")
598
- return script;
542
+ return $script;
599
543
  return (...args) => {
600
544
  const hookCtx2 = { script, fn, args };
601
545
  head.hooks.callHook("script:instance-fn", hookCtx2);
602
546
  if (head.ssr || !options.use)
603
547
  return;
604
- if (script.loaded) {
605
- const api = options.use();
606
- return api[fn](...args);
607
- } else {
608
- return script.waitForLoad().then(
609
- (api) => {
610
- return api[fn](...args);
611
- }
612
- );
613
- }
548
+ return script.status === "loaded" ? options.use()[fn](...args) : script.loadPromise.then((api) => api[fn](...args));
614
549
  };
615
550
  }
616
551
  });
617
- head._scripts = head._scripts || {};
618
- head._scripts[id] = instance;
552
+ head._scripts = Object.assign(
553
+ head._scripts || {},
554
+ { [id]: instance }
555
+ );
619
556
  return instance;
620
557
  }
621
558
 
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createHooks } from 'hookable';
2
2
  import { DomPlugin } from '@unhead/dom';
3
- import { defineHeadPlugin, tagDedupeKey, tagWeight, HasElementTags, NetworkEvents, hashCode, SortModifiers, processTemplateParams, resolveTitleTemplate, IsBrowser, normaliseEntryTags, composableNames, whitelistSafeInput, unpackMeta } from '@unhead/shared';
3
+ import { defineHeadPlugin, tagDedupeKey, tagWeight, HasElementTags, NetworkEvents, hashCode, SortModifiers, processTemplateParams, resolveTitleTemplate, IsBrowser, normaliseEntryTags, composableNames, whitelistSafeInput, unpackMeta, ScriptNetworkEvents } from '@unhead/shared';
4
4
  export { composableNames } from '@unhead/shared';
5
5
 
6
6
  const UsesMergeStrategy = ["templateParams", "htmlAttrs", "bodyAttrs"];
@@ -456,48 +456,16 @@ function useServerSeoMeta(input, options) {
456
456
  });
457
457
  }
458
458
 
459
- const UseScriptDefaults = {
460
- defer: true,
461
- fetchpriority: "low"
462
- };
463
- const requestIdleCallback = typeof window === "undefined" ? () => {
464
- } : globalThis.requestIdleCallback || ((cb) => {
465
- const start = Date.now();
466
- const idleDeadline = {
467
- didTimeout: false,
468
- timeRemaining: () => Math.max(0, 50 - (Date.now() - start))
469
- };
470
- return setTimeout(() => {
471
- cb(idleDeadline);
472
- }, 1);
473
- });
474
459
  function useScript(_input, _options) {
475
460
  const input = typeof _input === "string" ? { src: _input } : _input;
476
461
  const options = _options || {};
477
462
  const head = options.head || getActiveHead();
478
463
  if (!head)
479
- throw new Error("No active head found, please provide a head instance or use the useHead composable");
464
+ throw new Error("Missing Unhead context.");
480
465
  const id = input.key || hashCode(input.src || (typeof input.innerHTML === "string" ? input.innerHTML : ""));
481
466
  const key = `use-script.${id}`;
482
467
  if (head._scripts?.[id])
483
468
  return head._scripts[id];
484
- async function transform(entry) {
485
- const script2 = await (options.transform || ((input2) => input2))(entry.script[0]);
486
- const ctx = { script: script2 };
487
- await head.hooks.callHook("script:transform", ctx);
488
- return { script: [ctx.script] };
489
- }
490
- function maybeHintEarlyConnection(rel) {
491
- if (
492
- // opt-out
493
- options.skipEarlyConnections || !input.src.includes("//") || !head.ssr
494
- )
495
- return;
496
- const key2 = `use-script.${id}.early-connection`;
497
- head.push({
498
- link: [{ key: key2, rel, href: new URL(input.src).origin }]
499
- }, { mode: "server" });
500
- }
501
469
  const script = {
502
470
  id,
503
471
  status: "awaitingLoad",
@@ -512,109 +480,78 @@ function useScript(_input, _options) {
512
480
  }
513
481
  return false;
514
482
  },
515
- waitForLoad() {
516
- return new Promise((resolve) => {
517
- if (script.status === "loaded")
518
- resolve(options.use?.());
519
- function watchForScriptLoaded({ script: script2 }) {
520
- if (script2.id === id && script2.status === "loaded") {
521
- script2.loaded = true;
522
- resolve(options.use?.());
523
- head.hooks.removeHook("script:updated", watchForScriptLoaded);
524
- }
525
- }
526
- head.hooks.hook("script:updated", watchForScriptLoaded);
527
- });
528
- },
529
483
  load() {
530
484
  if (script.status !== "awaitingLoad")
531
- return script.waitForLoad();
485
+ return script.loadPromise;
532
486
  script.status = "loading";
533
487
  head.hooks.callHook(`script:updated`, hookCtx);
534
488
  script.entry = head.push({
535
489
  script: [
536
- // async by default
537
- { ...UseScriptDefaults, ...input, key }
490
+ { defer: true, fetchpriority: "low", ...input, key }
538
491
  ]
539
- }, {
540
- ...options,
541
- // @ts-expect-error untyped
542
- transform,
543
- head
544
- });
545
- return script.waitForLoad();
492
+ }, options);
493
+ return script.loadPromise;
546
494
  }
547
495
  };
496
+ script.loadPromise = new Promise((resolve, reject) => {
497
+ const removeHook2 = head.hooks.hook("script:updated", ({ script: script2 }) => {
498
+ if (script2.id === id && (script2.status === "loaded" || script2.status === "error")) {
499
+ script2.status === "loaded" && resolve(options.use?.());
500
+ script2.status === "error" && reject(new Error(`Failed to load script: ${input.src}`));
501
+ removeHook2();
502
+ }
503
+ });
504
+ });
548
505
  const hookCtx = { script };
549
- NetworkEvents.forEach((fn) => {
550
- const _fn = typeof input[fn] === "function" ? input[fn].bind({}) : null;
506
+ ScriptNetworkEvents.forEach((fn) => {
551
507
  input[fn] = (e) => {
552
508
  script.status = fn === "onload" ? "loaded" : fn === "onerror" ? "error" : "loading";
553
509
  head.hooks.callHook(`script:updated`, hookCtx);
554
- _fn && _fn(e);
510
+ typeof input[fn] === "function" && input[fn].call(options.eventContext, e);
555
511
  };
556
512
  });
557
- let trigger = options.trigger;
558
- if (trigger) {
559
- const isIdle = trigger === "idle";
560
- if (isIdle) {
561
- if (head.ssr)
562
- trigger = "manual";
563
- else
564
- trigger = new Promise((resolve) => requestIdleCallback(() => resolve()));
565
- }
566
- trigger === "manual" && (trigger = new Promise(() => {
567
- }));
513
+ const trigger = options.trigger;
514
+ if (options.trigger)
568
515
  trigger instanceof Promise && trigger.then(script.load);
569
- maybeHintEarlyConnection(isIdle ? "preconnect" : "dns-prefetch");
570
- } else {
516
+ else
571
517
  script.load();
572
- maybeHintEarlyConnection("preconnect");
573
- }
574
- function resolveInnerHtmlLoad(ctx) {
575
- if (ctx.tag.key === key) {
576
- if (ctx.tag.innerHTML) {
577
- setTimeout(
578
- () => {
579
- script.status = "loaded";
580
- head.hooks.callHook("script:updated", hookCtx);
581
- typeof input.onload === "function" && input.onload(new Event("load"));
582
- },
583
- 5
584
- /* give inline script a chance to run */
585
- );
586
- }
587
- head.hooks.removeHook("dom:renderTag", resolveInnerHtmlLoad);
518
+ const removeHook = head.hooks.hook("dom:renderTag", (ctx) => {
519
+ if (ctx.tag.key !== key)
520
+ return;
521
+ if (ctx.tag.innerHTML) {
522
+ setTimeout(
523
+ () => {
524
+ script.status = "loaded";
525
+ head.hooks.callHook("script:updated", hookCtx);
526
+ typeof input.onload === "function" && input.onload.call(options.eventContext, new Event("load"));
527
+ },
528
+ 5
529
+ /* give inline script a chance to run */
530
+ );
588
531
  }
589
- }
590
- head.hooks.hook("dom:renderTag", resolveInnerHtmlLoad);
532
+ removeHook();
533
+ });
591
534
  const instance = new Proxy({}, {
592
535
  get(_, fn) {
593
- const stub = options.stub?.({ script, fn });
536
+ const $script = Object.assign(script.loadPromise, script);
537
+ const stub = options.stub?.({ script: $script, fn });
594
538
  if (stub)
595
539
  return stub;
596
540
  if (fn === "$script")
597
- return script;
541
+ return $script;
598
542
  return (...args) => {
599
543
  const hookCtx2 = { script, fn, args };
600
544
  head.hooks.callHook("script:instance-fn", hookCtx2);
601
545
  if (head.ssr || !options.use)
602
546
  return;
603
- if (script.loaded) {
604
- const api = options.use();
605
- return api[fn](...args);
606
- } else {
607
- return script.waitForLoad().then(
608
- (api) => {
609
- return api[fn](...args);
610
- }
611
- );
612
- }
547
+ return script.status === "loaded" ? options.use()[fn](...args) : script.loadPromise.then((api) => api[fn](...args));
613
548
  };
614
549
  }
615
550
  });
616
- head._scripts = head._scripts || {};
617
- head._scripts[id] = instance;
551
+ head._scripts = Object.assign(
552
+ head._scripts || {},
553
+ { [id]: instance }
554
+ );
618
555
  return instance;
619
556
  }
620
557
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "unhead",
3
3
  "type": "module",
4
- "version": "1.8.19",
4
+ "version": "1.9.0",
5
5
  "author": {
6
6
  "name": "Harlan Wilton",
7
7
  "email": "harlan@harlanzw.com",
@@ -34,9 +34,9 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "hookable": "^5.5.3",
37
- "@unhead/dom": "1.8.19",
38
- "@unhead/schema": "1.8.19",
39
- "@unhead/shared": "1.8.19"
37
+ "@unhead/schema": "1.9.0",
38
+ "@unhead/shared": "1.9.0",
39
+ "@unhead/dom": "1.9.0"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "unbuild .",