unhead 1.8.0-beta.9 → 1.8.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
@@ -468,6 +468,10 @@ function useServerSeoMeta(input, options) {
468
468
  });
469
469
  }
470
470
 
471
+ const UseScriptDefaults = {
472
+ defer: true,
473
+ fetchpriority: "low"
474
+ };
471
475
  function useScript(input, _options) {
472
476
  const options = _options || {};
473
477
  const head = options.head || getActiveHead();
@@ -483,29 +487,53 @@ function useScript(input, _options) {
483
487
  await head.hooks.callHook("script:transform", ctx);
484
488
  return { script: [ctx.script] };
485
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
+ }
486
501
  const script = {
487
502
  id,
488
503
  status: "awaitingLoad",
489
504
  loaded: false,
490
- remove: () => {
505
+ remove() {
491
506
  if (script.status === "loaded") {
492
507
  script.entry?.dispose();
493
508
  script.status = "removed";
494
509
  head.hooks.callHook(`script:updated`, hookCtx);
510
+ delete head._scripts?.[id];
495
511
  return true;
496
512
  }
497
513
  return false;
498
514
  },
499
- waitForUse: () => new Promise(() => {
500
- }),
501
- load: () => {
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
+ resolve(options.use?.());
522
+ head.hooks.removeHook("script:updated", watchForScriptLoaded);
523
+ }
524
+ }
525
+ head.hooks.hook("script:updated", watchForScriptLoaded);
526
+ });
527
+ },
528
+ load() {
502
529
  if (script.status !== "awaitingLoad")
503
- return script.waitForUse();
530
+ return script.waitForLoad();
504
531
  script.status = "loading";
505
532
  head.hooks.callHook(`script:updated`, hookCtx);
506
533
  script.entry = head.push({
507
534
  script: [
508
- { ...input, key }
535
+ // async by default
536
+ { ...UseScriptDefaults, ...input, key }
509
537
  ]
510
538
  }, {
511
539
  ...options,
@@ -513,42 +541,47 @@ function useScript(input, _options) {
513
541
  transform,
514
542
  head
515
543
  });
516
- return script.waitForUse();
544
+ return script.waitForLoad();
517
545
  }
518
546
  };
519
547
  const hookCtx = { script };
520
548
  shared.NetworkEvents.forEach((fn) => {
521
549
  const _fn = typeof input[fn] === "function" ? input[fn].bind({}) : null;
522
- if (_fn) {
523
- input[fn] = (e) => {
524
- script.status = fn === "onload" ? "loaded" : fn === "onerror" ? "error" : "loading";
525
- head.hooks.callHook(`script:updated`, hookCtx);
526
- _fn && _fn(e);
527
- };
528
- }
550
+ input[fn] = (e) => {
551
+ script.status = fn === "onload" ? "loaded" : fn === "onerror" ? "error" : "loading";
552
+ head.hooks.callHook(`script:updated`, hookCtx);
553
+ _fn && _fn(e);
554
+ };
529
555
  });
530
556
  let trigger = options.trigger;
531
557
  if (trigger) {
532
- trigger === "idle" && (trigger = new Promise((resolve) => requestIdleCallback(() => resolve())));
558
+ const isIdle = trigger === "idle";
559
+ if (isIdle) {
560
+ if (head.ssr)
561
+ trigger = "manual";
562
+ else
563
+ trigger = new Promise((resolve) => requestIdleCallback(() => resolve()));
564
+ }
533
565
  trigger === "manual" && (trigger = new Promise(() => {
534
566
  }));
535
567
  trigger instanceof Promise && trigger.then(script.load);
568
+ maybeHintEarlyConnection(isIdle ? "preconnect" : "dns-prefetch");
536
569
  } else {
537
570
  script.load();
571
+ maybeHintEarlyConnection("preconnect");
538
572
  }
539
- script.waitForUse = () => new Promise((resolve) => {
540
- if (typeof options.use === "function") {
541
- if (script.status === "loaded")
542
- resolve(options.use());
543
- head.hooks.hook("script:loaded", ({ script: script2 }) => script2.id === id && resolve(options.use()));
544
- }
545
- });
546
573
  function resolveInnerHtmlLoad(ctx) {
547
574
  if (ctx.tag.key === key) {
548
575
  if (ctx.tag.innerHTML) {
549
- script.status = "loaded";
550
- head.hooks.callHook("script:updated", hookCtx);
551
- typeof input.onload === "function" && input.onload(new Event("load"));
576
+ setTimeout(
577
+ () => {
578
+ script.status = "loaded";
579
+ head.hooks.callHook("script:updated", hookCtx);
580
+ typeof input.onload === "function" && input.onload(new Event("load"));
581
+ },
582
+ 5
583
+ /* give inline script a chance to run */
584
+ );
552
585
  }
553
586
  head.hooks.removeHook("dom:renderTag", resolveInnerHtmlLoad);
554
587
  }
@@ -568,7 +601,7 @@ function useScript(input, _options) {
568
601
  const api = options.use();
569
602
  return api[fn](...args);
570
603
  } else {
571
- return script.waitForUse().then(
604
+ return script.waitForLoad().then(
572
605
  (api) => {
573
606
  api[fn](...args);
574
607
  }
package/dist/index.d.cts CHANGED
@@ -41,6 +41,12 @@ declare function useSeoMeta(input: UseSeoMetaInput, options?: HeadEntryOptions):
41
41
 
42
42
  declare function useServerSeoMeta(input: UseSeoMetaInput, options?: HeadEntryOptions): ActiveHeadEntry<any> | void;
43
43
 
44
+ /**
45
+ * Load third-party scripts with SSR support and a proxied API.
46
+ *
47
+ * @experimental
48
+ * @see https://unhead.unjs.io/usage/composables/use-script
49
+ */
44
50
  declare function useScript<T>(input: UseScriptInput, _options?: UseScriptOptions<T>): T & {
45
51
  $script: ScriptInstance<T>;
46
52
  };
package/dist/index.d.mts CHANGED
@@ -41,6 +41,12 @@ declare function useSeoMeta(input: UseSeoMetaInput, options?: HeadEntryOptions):
41
41
 
42
42
  declare function useServerSeoMeta(input: UseSeoMetaInput, options?: HeadEntryOptions): ActiveHeadEntry<any> | void;
43
43
 
44
+ /**
45
+ * Load third-party scripts with SSR support and a proxied API.
46
+ *
47
+ * @experimental
48
+ * @see https://unhead.unjs.io/usage/composables/use-script
49
+ */
44
50
  declare function useScript<T>(input: UseScriptInput, _options?: UseScriptOptions<T>): T & {
45
51
  $script: ScriptInstance<T>;
46
52
  };
package/dist/index.d.ts CHANGED
@@ -41,6 +41,12 @@ declare function useSeoMeta(input: UseSeoMetaInput, options?: HeadEntryOptions):
41
41
 
42
42
  declare function useServerSeoMeta(input: UseSeoMetaInput, options?: HeadEntryOptions): ActiveHeadEntry<any> | void;
43
43
 
44
+ /**
45
+ * Load third-party scripts with SSR support and a proxied API.
46
+ *
47
+ * @experimental
48
+ * @see https://unhead.unjs.io/usage/composables/use-script
49
+ */
44
50
  declare function useScript<T>(input: UseScriptInput, _options?: UseScriptOptions<T>): T & {
45
51
  $script: ScriptInstance<T>;
46
52
  };
package/dist/index.mjs CHANGED
@@ -467,6 +467,10 @@ function useServerSeoMeta(input, options) {
467
467
  });
468
468
  }
469
469
 
470
+ const UseScriptDefaults = {
471
+ defer: true,
472
+ fetchpriority: "low"
473
+ };
470
474
  function useScript(input, _options) {
471
475
  const options = _options || {};
472
476
  const head = options.head || getActiveHead();
@@ -482,29 +486,53 @@ function useScript(input, _options) {
482
486
  await head.hooks.callHook("script:transform", ctx);
483
487
  return { script: [ctx.script] };
484
488
  }
489
+ function maybeHintEarlyConnection(rel) {
490
+ if (
491
+ // opt-out
492
+ options.skipEarlyConnections || !input.src.includes("//") || !head.ssr
493
+ )
494
+ return;
495
+ const key2 = `use-script.${id}.early-connection`;
496
+ head.push({
497
+ link: [{ key: key2, rel, href: new URL(input.src).origin }]
498
+ }, { mode: "server" });
499
+ }
485
500
  const script = {
486
501
  id,
487
502
  status: "awaitingLoad",
488
503
  loaded: false,
489
- remove: () => {
504
+ remove() {
490
505
  if (script.status === "loaded") {
491
506
  script.entry?.dispose();
492
507
  script.status = "removed";
493
508
  head.hooks.callHook(`script:updated`, hookCtx);
509
+ delete head._scripts?.[id];
494
510
  return true;
495
511
  }
496
512
  return false;
497
513
  },
498
- waitForUse: () => new Promise(() => {
499
- }),
500
- load: () => {
514
+ waitForLoad() {
515
+ return new Promise((resolve) => {
516
+ if (script.status === "loaded")
517
+ resolve(options.use());
518
+ function watchForScriptLoaded({ script: script2 }) {
519
+ if (script2.id === id && script2.status === "loaded") {
520
+ resolve(options.use?.());
521
+ head.hooks.removeHook("script:updated", watchForScriptLoaded);
522
+ }
523
+ }
524
+ head.hooks.hook("script:updated", watchForScriptLoaded);
525
+ });
526
+ },
527
+ load() {
501
528
  if (script.status !== "awaitingLoad")
502
- return script.waitForUse();
529
+ return script.waitForLoad();
503
530
  script.status = "loading";
504
531
  head.hooks.callHook(`script:updated`, hookCtx);
505
532
  script.entry = head.push({
506
533
  script: [
507
- { ...input, key }
534
+ // async by default
535
+ { ...UseScriptDefaults, ...input, key }
508
536
  ]
509
537
  }, {
510
538
  ...options,
@@ -512,42 +540,47 @@ function useScript(input, _options) {
512
540
  transform,
513
541
  head
514
542
  });
515
- return script.waitForUse();
543
+ return script.waitForLoad();
516
544
  }
517
545
  };
518
546
  const hookCtx = { script };
519
547
  NetworkEvents.forEach((fn) => {
520
548
  const _fn = typeof input[fn] === "function" ? input[fn].bind({}) : null;
521
- if (_fn) {
522
- input[fn] = (e) => {
523
- script.status = fn === "onload" ? "loaded" : fn === "onerror" ? "error" : "loading";
524
- head.hooks.callHook(`script:updated`, hookCtx);
525
- _fn && _fn(e);
526
- };
527
- }
549
+ input[fn] = (e) => {
550
+ script.status = fn === "onload" ? "loaded" : fn === "onerror" ? "error" : "loading";
551
+ head.hooks.callHook(`script:updated`, hookCtx);
552
+ _fn && _fn(e);
553
+ };
528
554
  });
529
555
  let trigger = options.trigger;
530
556
  if (trigger) {
531
- trigger === "idle" && (trigger = new Promise((resolve) => requestIdleCallback(() => resolve())));
557
+ const isIdle = trigger === "idle";
558
+ if (isIdle) {
559
+ if (head.ssr)
560
+ trigger = "manual";
561
+ else
562
+ trigger = new Promise((resolve) => requestIdleCallback(() => resolve()));
563
+ }
532
564
  trigger === "manual" && (trigger = new Promise(() => {
533
565
  }));
534
566
  trigger instanceof Promise && trigger.then(script.load);
567
+ maybeHintEarlyConnection(isIdle ? "preconnect" : "dns-prefetch");
535
568
  } else {
536
569
  script.load();
570
+ maybeHintEarlyConnection("preconnect");
537
571
  }
538
- script.waitForUse = () => new Promise((resolve) => {
539
- if (typeof options.use === "function") {
540
- if (script.status === "loaded")
541
- resolve(options.use());
542
- head.hooks.hook("script:loaded", ({ script: script2 }) => script2.id === id && resolve(options.use()));
543
- }
544
- });
545
572
  function resolveInnerHtmlLoad(ctx) {
546
573
  if (ctx.tag.key === key) {
547
574
  if (ctx.tag.innerHTML) {
548
- script.status = "loaded";
549
- head.hooks.callHook("script:updated", hookCtx);
550
- typeof input.onload === "function" && input.onload(new Event("load"));
575
+ setTimeout(
576
+ () => {
577
+ script.status = "loaded";
578
+ head.hooks.callHook("script:updated", hookCtx);
579
+ typeof input.onload === "function" && input.onload(new Event("load"));
580
+ },
581
+ 5
582
+ /* give inline script a chance to run */
583
+ );
551
584
  }
552
585
  head.hooks.removeHook("dom:renderTag", resolveInnerHtmlLoad);
553
586
  }
@@ -567,7 +600,7 @@ function useScript(input, _options) {
567
600
  const api = options.use();
568
601
  return api[fn](...args);
569
602
  } else {
570
- return script.waitForUse().then(
603
+ return script.waitForLoad().then(
571
604
  (api) => {
572
605
  api[fn](...args);
573
606
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "unhead",
3
3
  "type": "module",
4
- "version": "1.8.0-beta.9",
4
+ "version": "1.8.1",
5
5
  "author": "Harlan Wilton <harlan@harlanzw.com>",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/harlan-zw",
@@ -30,9 +30,9 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "hookable": "^5.5.3",
33
- "@unhead/dom": "1.8.0-beta.9",
34
- "@unhead/schema": "1.8.0-beta.9",
35
- "@unhead/shared": "1.8.0-beta.9"
33
+ "@unhead/dom": "1.8.1",
34
+ "@unhead/schema": "1.8.1",
35
+ "@unhead/shared": "1.8.1"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "unbuild .",