@exabytellc/utils
Version:
EB react utils to make everything a little easier!
157 lines (144 loc) • 5.87 kB
JavaScript
import buildFilePlugin from "./buildFilePlugin.js";
/**
* Vite Plugin to Generate a Sitemap XML File.
* @param {Object} options - Plugin options
* @param {string} options.host - The website's base URL (e.g., "https://example.com").
* @param {Array<Object>} [options.routes=[]] - An array of static or dynamic routes for the sitemap.
* @returns {Object} Vite plugin configuration
*/
export default function generateSitemapPlugin({ host, routes = [] }) {
return buildFilePlugin("vite-generate-sitemap-plugin", {
filename: "sitemap.xml",
content: async (config) => {
return await generateSitemap(host, config.base, routes);
},
});
/**
* Generates the sitemap XML content.
* @param {string} host - The base URL of the website.
* @param {string} base - The base path for the site.
* @param {Array<Object>} routes - Array of route objects or functions.
* @returns {Promise<string>} The generated sitemap XML string.
*/
async function generateSitemap(host, base, routes) {
const urls = await Promise.all(
routes.flatMap(async ({ route, lastmod, changefreq, priority }) => {
if (isFunc(route)) {
return (await route()).map((r) =>
formatRoute(host, base, r, { lastmod, changefreq, priority })
);
} else {
return formatRoute(host, base, route, { lastmod, changefreq, priority });
}
})
);
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.join("\n ")}
</urlset>`;
}
/**
* Formats a route into a sitemap entry.
* @param {string} host - Base website URL.
* @param {string} base - Base path for the site.
* @param {string} route - Page route.
* @param {Object} options - Additional metadata.
* @param {string} [options.lastmod] - Last modified date (ISO format).
* @param {string} [options.changefreq] - Change frequency (e.g., "daily").
* @param {number} [options.priority] - Priority value (0.0 - 1.0).
* @returns {string} XML-formatted route entry.
*/
function formatRoute(host, base, route, { lastmod, changefreq, priority }) {
if (!validateRoute(route)) return "";
return `<url>
<loc>${formatUrl(host, base, route)}</loc>
${lastmod ? `<lastmod>${lastmod}</lastmod>` : ""}
${changefreq ? `<changefreq>${changefreq}</changefreq>` : ""}
${priority ? `<priority>${priority}</priority>` : ""}
</url>`;
}
/**
* Ensures proper URL formatting.
* @param {string} host - Base website URL.
* @param {string} base - Base path.
* @param {string} route - Route path.
* @returns {string} Formatted URL.
*/
function formatUrl(host, base, route) {
return `${host}${base}${route}`.replace(/\/+/g, "/").replace(/\/$/, "");
}
/**
* Validates if a given route is valid.
* @param {string} route - The route to validate.
* @returns {boolean} True if valid, false otherwise.
*/
function validateRoute(route) {
return typeof route === "string" && route.length > 0 && !route.includes(" ");
}
/**
* Checks if a value is a function.
* @param {*} value - The value to check.
* @param {boolean} [canBeNull=false] - Whether null values are acceptable.
* @returns {boolean} True if a function, false otherwise.
*/
function isFunc(value, canBeNull = false) {
return (
(canBeNull && isNull(value)) ||
(!isNull(value) &&
typeof value === "function" &&
["AsyncFunction", "Function"].includes(value?.constructor?.name))
);
}
/**
* Checks if a value is null, undefined, or NaN.
* @param {*} value - The value to check.
* @returns {boolean} True if null-like, false otherwise.
*/
function isNull(value) {
return [null, undefined, NaN].includes(value);
}
}
/**
* Creates a static sitemap route entry.
* @param {string} route - The route path.
* @param {Object} [options] - Additional metadata.
* @param {string} [options.lastUpdated] - Last modified date (ISO format).
* @param {string} [options.changefreq="monthly"] - Change frequency.
* @param {number} [options.priority=0.5] - Priority value.
* @returns {Object} Sitemap route object.
*/
export function sitemapRoute(route, { lastUpdated, changefreq, priority } = {}) {
return {
route: route,
lastmod: lastUpdated || new Date().toISOString(),
changefreq: changefreq || "monthly",
priority: priority || 0.5,
};
}
/**
* Fetches dynamic routes from an API and formats them for the sitemap.
* @param {string} api - The API URL.
* @param {Function} process - Function to process API response into routes.
* @param {Object} [options] - Additional metadata.
* @param {string} [options.lastUpdated] - Last modified date (ISO format).
* @param {string} [options.changefreq="weekly"] - Change frequency.
* @param {number} [options.priority=0.8] - Priority value.
* @returns {Promise<Array<Object>>} A list of formatted dynamic routes.
*/
export async function sitemapDynamicRoute(api, process, { processData, lastUpdated, changefreq, priority } = {}) {
try {
const response = await fetch(api);
if (!response.ok) throw new Error(`API Error: ${response.status}`);
var data = await response.json();
if (processData) data = processData(data)
return data.map((item) => ({
route: process(item), // Process API data into a route path
lastmod: lastUpdated || item.lastUpdated || new Date().toISOString(),
changefreq: changefreq || "weekly",
priority: priority || 0.8,
}));
} catch (error) {
console.error("❌ Failed to fetch dynamic routes:", error);
return []; // Return an empty array to prevent sitemap errors
}
}