sitemap-ts
Version:
Sitemap generator for TypeScript projects
165 lines (153 loc) • 6.26 kB
JavaScript
;
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;