url-set-builder.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { removeIfMatchPattern } from '../utils/array.js';
  2. import { defaultSitemapTransformer } from '../utils/defaults.js';
  3. import { createDefaultLocaleReplace, entityEscapedUrl, generateUrl, isNextInternalUrl, } from '../utils/url.js';
  4. export class UrlSetBuilder {
  5. config;
  6. manifest;
  7. constructor(config, manifest) {
  8. this.config = config;
  9. this.manifest = manifest;
  10. }
  11. /**
  12. * Returns absolute url by combining siteUrl and path w.r.t trailingSlash config
  13. * @param siteUrl
  14. * @param path
  15. * @param trailingSlash
  16. * @returns
  17. */
  18. absoluteUrl(siteUrl, path, trailingSlash) {
  19. const url = generateUrl(siteUrl, trailingSlash ? `${path}/` : path);
  20. if (!trailingSlash && url.endsWith('/')) {
  21. return url.slice(0, url.length - 1);
  22. }
  23. return entityEscapedUrl(url);
  24. }
  25. /**
  26. * Normalize sitemap fields to include absolute urls
  27. * @param field
  28. */
  29. normalizeSitemapField(field) {
  30. // Handle trailing Slash
  31. const trailingSlash = 'trailingSlash' in field
  32. ? field.trailingSlash
  33. : this.config?.trailingSlash;
  34. return {
  35. ...field,
  36. trailingSlash,
  37. loc: this.absoluteUrl(this.config?.siteUrl, field?.loc, trailingSlash),
  38. alternateRefs: (field.alternateRefs ?? []).map((alternateRef) => ({
  39. href: alternateRef.hrefIsAbsolute
  40. ? alternateRef.href
  41. : this.absoluteUrl(alternateRef.href, field.loc, trailingSlash),
  42. hreflang: alternateRef.hreflang,
  43. })),
  44. };
  45. }
  46. /**
  47. * Create a unique url set
  48. */
  49. async createUrlSet() {
  50. // Load i18n routes
  51. const i18n = this.manifest?.routes?.i18n;
  52. // Init all page keys
  53. const allKeys = [
  54. ...Object.keys(this.manifest?.build.pages),
  55. ...(this.manifest?.build?.ampFirstPages ?? []),
  56. ...(this.manifest?.preRender
  57. ? Object.keys(this.manifest?.preRender.routes)
  58. : []),
  59. ];
  60. // Filter out next.js internal urls and generate urls based on sitemap
  61. let urlSet = allKeys.filter((x) => !isNextInternalUrl(x));
  62. // Remove default locale if i18n is enabled
  63. let defaultLocale;
  64. if (i18n) {
  65. defaultLocale = i18n.defaultLocale;
  66. const replaceDefaultLocale = createDefaultLocaleReplace(defaultLocale);
  67. urlSet = urlSet.map(replaceDefaultLocale);
  68. }
  69. // Remove the urls based on this.config?.exclude array
  70. if (this.config?.exclude && this.config?.exclude.length > 0) {
  71. urlSet = removeIfMatchPattern(urlSet, this.config?.exclude);
  72. }
  73. urlSet = [...new Set(urlSet)];
  74. // Remove routes which don't exist
  75. const notFoundRoutes = (this.manifest?.preRender?.notFoundRoutes ??
  76. []);
  77. urlSet = urlSet.filter((url) => {
  78. return (!notFoundRoutes.includes(url) &&
  79. !notFoundRoutes.includes(`/${defaultLocale}${url}`));
  80. });
  81. // Create sitemap fields based on transformation
  82. const sitemapFields = []; // transform using relative urls
  83. // Create a map of fields by loc to quickly find collisions
  84. const mapFieldsByLoc = {};
  85. for (const url of urlSet) {
  86. const sitemapField = await this.config?.transform?.(this.config, url);
  87. if (!sitemapField?.loc)
  88. continue;
  89. sitemapFields.push(sitemapField);
  90. // Add link on field to map by loc
  91. if (this.config?.additionalPaths) {
  92. mapFieldsByLoc[sitemapField.loc] = sitemapField;
  93. }
  94. }
  95. if (this.config?.additionalPaths) {
  96. const additions = (await this.config?.additionalPaths({
  97. ...this.config,
  98. transform: this.config?.transform ?? defaultSitemapTransformer,
  99. })) ?? [];
  100. for (const field of additions) {
  101. if (!field?.loc)
  102. continue;
  103. const collision = mapFieldsByLoc[field.loc];
  104. // Update first entry
  105. if (collision) {
  106. // Mutate common entry between sitemapFields and mapFieldsByLoc (spread operator don't work)
  107. Object.entries(field).forEach(([key, value]) => (collision[key] = value));
  108. continue;
  109. }
  110. sitemapFields.push(field);
  111. }
  112. }
  113. return sitemapFields.map((x) => this.normalizeSitemapField(x));
  114. }
  115. }