exportable-builder.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { SitemapBuilder } from './sitemap-builder.js';
  2. import path from 'node:path';
  3. import { generateUrl } from '../utils/url.js';
  4. import { combineMerge } from '../utils/merge.js';
  5. import { RobotsTxtBuilder } from './robots-txt-builder.js';
  6. import { defaultRobotsTxtTransformer } from '../utils/defaults.js';
  7. import { exportFile } from '../utils/file.js';
  8. export class ExportableBuilder {
  9. exportableList = [];
  10. config;
  11. runtimePaths;
  12. sitemapBuilder;
  13. robotsTxtBuilder;
  14. exportDir;
  15. constructor(config, runtimePaths) {
  16. this.config = config;
  17. this.runtimePaths = runtimePaths;
  18. this.sitemapBuilder = new SitemapBuilder();
  19. this.robotsTxtBuilder = new RobotsTxtBuilder();
  20. this.exportDir = path.resolve(process.cwd(), this.config.outDir);
  21. }
  22. /**
  23. * Register sitemap index files
  24. */
  25. async registerIndexSitemap() {
  26. // Get generated sitemap list
  27. const sitemaps = [
  28. ...this.generatedSitemaps(),
  29. // Include additionalSitemaps provided via robots.txt options
  30. ...(this.config?.robotsTxtOptions?.additionalSitemaps ?? []),
  31. ];
  32. // Generate sitemap-index content
  33. const content = this.sitemapBuilder.buildSitemapIndexXml(sitemaps);
  34. // Create exportable
  35. const item = {
  36. type: 'sitemap-index',
  37. filename: this.runtimePaths.SITEMAP_INDEX_FILE,
  38. url: this.runtimePaths.SITEMAP_INDEX_URL,
  39. content,
  40. };
  41. // Add to exportable list
  42. this.exportableList.push(item);
  43. }
  44. /**
  45. * Resolve filename if index sitemap is generated
  46. * @param index
  47. * @returns
  48. */
  49. resolveFilenameWithIndexSitemap(index) {
  50. return `${this.config.sitemapBaseFileName}-${index}.xml`;
  51. }
  52. /**
  53. * Resolve filename if index sitemaps is not generated
  54. * @param index
  55. * @returns
  56. */
  57. resolveFilenameWithoutIndexSitemap(index) {
  58. if (index === 0) {
  59. return `${this.config.sitemapBaseFileName}.xml`;
  60. }
  61. return this.resolveFilenameWithIndexSitemap(index);
  62. }
  63. /**
  64. * Register sitemaps with exportable builder
  65. * @param chunks
  66. */
  67. async registerSitemaps(chunks) {
  68. // Check whether user config allows sitemap generation
  69. const hasIndexSitemap = this.config.generateIndexSitemap;
  70. // Create exportable items
  71. const items = chunks?.map((chunk, index) => {
  72. // Get sitemap base filename
  73. const baseFilename = hasIndexSitemap
  74. ? this.resolveFilenameWithIndexSitemap(index)
  75. : this.resolveFilenameWithoutIndexSitemap(index);
  76. return {
  77. type: 'sitemap',
  78. url: generateUrl(this.config.siteUrl, baseFilename),
  79. filename: path.resolve(this.exportDir, baseFilename),
  80. content: this.sitemapBuilder.buildSitemapXml(chunk),
  81. };
  82. });
  83. // Add to exportable list
  84. this.exportableList.push(...items);
  85. }
  86. /**
  87. * Get robots.txt export config
  88. * @returns
  89. */
  90. robotsTxtExportConfig() {
  91. // Endpoints list
  92. const endpoints = [];
  93. // Include non-index sitemaps
  94. // Optionally allow user to include non-index sitemaps along with generated sitemap list
  95. // Set to true if index-sitemap is not generated
  96. const includeNonIndexSitemaps = this.config.generateIndexSitemap
  97. ? this.config?.robotsTxtOptions?.includeNonIndexSitemaps
  98. : true;
  99. // Add all sitemap indices
  100. if (this.config.generateIndexSitemap) {
  101. endpoints.push(...this.generatedSitemapIndices());
  102. }
  103. // Add all non-index sitemaps
  104. if (includeNonIndexSitemaps) {
  105. endpoints.push(...this.generatedSitemaps());
  106. }
  107. // Combine merge with additional sitemaps
  108. return combineMerge({
  109. robotsTxtOptions: {
  110. additionalSitemaps: endpoints,
  111. },
  112. }, this.config);
  113. }
  114. /**
  115. * Register robots.txt export
  116. */
  117. async registerRobotsTxt() {
  118. // File name of robots.txt
  119. const baseFilename = 'robots.txt';
  120. // Export config of robots.txt
  121. const robotsConfig = this.robotsTxtExportConfig();
  122. // Generate robots content
  123. let content = this.robotsTxtBuilder.generateRobotsTxt(robotsConfig);
  124. // Get robots transformer
  125. const robotsTransformer = robotsConfig?.robotsTxtOptions?.transformRobotsTxt ??
  126. defaultRobotsTxtTransformer;
  127. // Transform generated robots txt
  128. content = await robotsTransformer(robotsConfig, content);
  129. // Generate exportable item
  130. const item = {
  131. type: 'robots.txt',
  132. filename: path.resolve(this.exportDir, baseFilename),
  133. url: generateUrl(robotsConfig?.siteUrl, baseFilename),
  134. content,
  135. };
  136. // Add to exportableList
  137. this.exportableList.push(item);
  138. }
  139. /**
  140. * Generic reducer to extract by type
  141. * @param condition
  142. * @returns
  143. */
  144. exportableUrlReducer(condition) {
  145. return this.exportableList.reduce((prev, curr) => {
  146. const matches = condition(curr);
  147. if (matches) {
  148. prev.push(curr.url);
  149. }
  150. return prev;
  151. }, []);
  152. }
  153. /**
  154. * Return a lit of sitemap urls
  155. * @returns
  156. */
  157. generatedSitemaps() {
  158. return this.exportableUrlReducer((x) => x.type == 'sitemap');
  159. }
  160. /**
  161. * Generate sitemap indices
  162. * @returns
  163. */
  164. generatedSitemapIndices() {
  165. return this.exportableUrlReducer((x) => x.type == 'sitemap-index');
  166. }
  167. /**
  168. * Export all registered files
  169. * @returns
  170. */
  171. async exportAll() {
  172. await Promise.all(this.exportableList?.map(async (item) => exportFile(item.filename, item.content)));
  173. // Create result object
  174. return {
  175. runtimePaths: this.runtimePaths,
  176. sitemaps: this.generatedSitemaps(),
  177. sitemapIndices: this.generatedSitemapIndices(),
  178. };
  179. }
  180. }