UNPKG

@exabytellc/utils

Version:

EB react utils to make everything a little easier!

157 lines (144 loc) 5.87 kB
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 } }