head.js 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { watchEffect } from 'vue';
  2. export function useUpdateHead(route, siteDataByRouteRef) {
  3. let managedHeadTags = [];
  4. let isFirstUpdate = true;
  5. const updateHeadTags = (newTags) => {
  6. if (import.meta.env.PROD && isFirstUpdate) {
  7. // in production, the initial meta tags are already pre-rendered so we
  8. // skip the first update.
  9. isFirstUpdate = false;
  10. return;
  11. }
  12. const newEls = [];
  13. const commonLength = Math.min(managedHeadTags.length, newTags.length);
  14. for (let i = 0; i < commonLength; i++) {
  15. let el = managedHeadTags[i];
  16. const [tag, attrs, innerHTML = ''] = newTags[i];
  17. if (el.tagName.toLocaleLowerCase() === tag) {
  18. for (const key in attrs) {
  19. if (el.getAttribute(key) !== attrs[key]) {
  20. el.setAttribute(key, attrs[key]);
  21. }
  22. }
  23. for (let i = 0; i < el.attributes.length; i++) {
  24. const name = el.attributes[i].name;
  25. if (!(name in attrs)) {
  26. el.removeAttribute(name);
  27. }
  28. }
  29. if (el.innerHTML !== innerHTML) {
  30. el.innerHTML = innerHTML;
  31. }
  32. }
  33. else {
  34. document.head.removeChild(el);
  35. el = createHeadElement(newTags[i]);
  36. document.head.append(el);
  37. }
  38. newEls.push(el);
  39. }
  40. managedHeadTags
  41. .slice(commonLength)
  42. .forEach((el) => document.head.removeChild(el));
  43. newTags.slice(commonLength).forEach((headConfig) => {
  44. const el = createHeadElement(headConfig);
  45. document.head.appendChild(el);
  46. newEls.push(el);
  47. });
  48. managedHeadTags = newEls;
  49. };
  50. watchEffect(() => {
  51. const pageData = route.data;
  52. const siteData = siteDataByRouteRef.value;
  53. const pageTitle = pageData && pageData.title;
  54. const pageDescription = pageData && pageData.description;
  55. const frontmatterHead = pageData && pageData.frontmatter.head;
  56. // update title and description
  57. document.title = (pageTitle ? pageTitle + ` | ` : ``) + siteData.title;
  58. document
  59. .querySelector(`meta[name=description]`)
  60. .setAttribute('content', pageDescription || siteData.description);
  61. updateHeadTags([
  62. // site head can only change during dev
  63. ...(import.meta.env.DEV ? siteData.head : []),
  64. ...(frontmatterHead ? filterOutHeadDescription(frontmatterHead) : [])
  65. ]);
  66. });
  67. }
  68. function createHeadElement([tag, attrs, innerHTML]) {
  69. const el = document.createElement(tag);
  70. for (const key in attrs) {
  71. el.setAttribute(key, attrs[key]);
  72. }
  73. if (innerHTML) {
  74. el.innerHTML = innerHTML;
  75. }
  76. return el;
  77. }
  78. function isMetaDescription(headConfig) {
  79. return (headConfig[0] === 'meta' &&
  80. headConfig[1] &&
  81. headConfig[1].name === 'description');
  82. }
  83. function filterOutHeadDescription(head) {
  84. return head.filter((h) => !isMetaDescription(h));
  85. }