UNPKG

sitemap-ts

Version:

Sitemap generator for TypeScript projects

165 lines (153 loc) 6.26 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); const node_fs = require('node:fs'); const sitemap = require('sitemap'); const format = require('xml-formatter'); const utils = require('@antfu/utils'); const node_path = require('node:path'); const fg = require('fast-glob'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } const format__default = /*#__PURE__*/_interopDefaultCompat(format); const fg__default = /*#__PURE__*/_interopDefaultCompat(fg); const defaultOptions = { hostname: "http://localhost/", dynamicRoutes: [], exclude: [], externalSitemaps: [], basePath: "", outDir: "dist", extensions: "html", changefreq: "daily", priority: 1, lastmod: /* @__PURE__ */ new Date(), readable: false, generateRobotsTxt: true, robots: [{ userAgent: "*", allow: "/" }] }; function resolveOptions(userOptions) { return Object.assign( {}, defaultOptions, userOptions ); } function getResolvedPath(file, resolvedOptions) { if (node_path.isAbsolute(resolvedOptions.outDir)) return node_path.resolve(`${resolvedOptions.outDir}/${file}`); return node_path.resolve(`${utils.ensurePrefix("./", resolvedOptions.outDir)}/${file}`); } function removeMaybeSuffix(suffix, str) { if (!str.endsWith(suffix)) return str; return str.slice(0, -suffix.length); } var RobotCorrespondences = /* @__PURE__ */ ((RobotCorrespondences2) => { RobotCorrespondences2["userAgent"] = "User-agent"; RobotCorrespondences2["allow"] = "Allow"; RobotCorrespondences2["disallow"] = "Disallow"; RobotCorrespondences2["crawlDelay"] = "Crawl-delay"; RobotCorrespondences2["cleanParam"] = "Clean-param"; return RobotCorrespondences2; })(RobotCorrespondences || {}); function getRules(options) { const rules = []; options.forEach((rule) => { const keys = Object.keys(RobotCorrespondences).filter((key) => typeof rule[key] !== "undefined"); keys.forEach((key) => { const values = Array.isArray(rule[key]) ? rule[key] : [rule[key]]; values.forEach((value) => { rules.push({ key: RobotCorrespondences[key], value }); }); }); }); return rules; } function getContent(rules, hostname, externalSitemaps) { return rules.map((rule) => `${rule.key}: ${String(rule.value).trim()}`).join("\n").concat(` Sitemap: ${getFinalSitemapPath(hostname)}`).concat(externalSitemaps.map((s) => ` Sitemap: ${s.startsWith("http") ? s : getFinalSitemapPath(hostname, s)}`).join("")); } function getFinalSitemapPath(hostname, file = "/sitemap.xml") { return `${removeMaybeSuffix("/", hostname)}${utils.ensurePrefix("/", file)}`; } function getRoutes(options) { const ext = typeof options.extensions === "string" ? [options.extensions] : options.extensions; const strExt = ext.map((e) => `**/*.${e}`); return [ ...fg__default.sync(strExt, { cwd: options.outDir }).map((route) => { let r = route; ext.forEach((e) => { const regex = new RegExp(`index.${e}`, "g"); r = r.replace(regex, ""); }); const parsedRoute = node_path.parse(r); return utils.slash(node_path.join("/", parsedRoute.dir, parsedRoute.name)); }), ...options.dynamicRoutes.map((route) => utils.slash(node_path.join("/", node_path.join(node_path.parse(route).dir, node_path.parse(route).name)))) ].filter((route) => !options.exclude.includes(route)); } function getOptionByRoute(options, route) { if (options instanceof Date || typeof options === "string" || typeof options === "number") return options; const givenRoutes = Object.keys(options); if (givenRoutes.includes(route)) return options[route]; if (givenRoutes.includes("*")) return options["*"]; return void 0; } function getFormattedSitemap(options, routes) { return routes.map((route) => { const hostNamePath = removeMaybeSuffix("/", options.hostname); const routePath = options.basePath ? utils.ensurePrefix("/", options.basePath) + utils.ensurePrefix("/", route) : utils.ensurePrefix("/", route); const url = new URL(routePath, hostNamePath).href; const formattedSitemap = { url, changefreq: getOptionByRoute(options.changefreq, route) ?? defaultOptions.changefreq, priority: getOptionByRoute(options.priority, route) ?? defaultOptions.priority, lastmod: getOptionByRoute(options.lastmod, route) ?? defaultOptions.lastmod }; if (options.i18n) { const strategy = options.i18n.strategy ?? "suffix"; const languages = options.i18n.languages.map((str) => ({ lang: str, url: str === options.i18n?.defaultLanguage ? url : new URL(strategy === "prefix" ? utils.ensurePrefix("/", str) + routePath : removeMaybeSuffix("/", routePath) + utils.ensurePrefix("/", str), hostNamePath).href })); return Object.assign(formattedSitemap, { links: options.i18n.defaultLanguage ? [...languages, { lang: "x-default", url }] : languages }); } return formattedSitemap; }); } function generateSitemap(options = {}) { const resolvedOptions = resolveOptions(options); if (resolvedOptions.generateRobotsTxt) { const robotRules = getRules(resolvedOptions.robots); const robotContent = getContent(robotRules, resolvedOptions.hostname, resolvedOptions.externalSitemaps); node_fs.writeFileSync(getResolvedPath("robots.txt", resolvedOptions), robotContent); } const routes = getRoutes(resolvedOptions); if (!routes.length) return; const formattedSitemap = getFormattedSitemap(resolvedOptions, routes); const stream = new sitemap.SitemapStream({ xmlns: resolvedOptions.xmlns }); formattedSitemap.forEach((item) => stream.write(item)); sitemap.streamToPromise(stream).then((sitemap) => { const utfSitemap = sitemap.toString("utf-8"); const formattedSitemap2 = resolvedOptions.readable ? format__default(utfSitemap) : utfSitemap; node_fs.writeFileSync(getResolvedPath("sitemap.xml", resolvedOptions), formattedSitemap2); }); stream.end(); } exports.default = generateSitemap; exports.generateSitemap = generateSitemap; exports.getFormattedSitemap = getFormattedSitemap; exports.getRoutes = getRoutes; exports.resolveOptions = resolveOptions;