alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
131 lines (130 loc) • 4.5 kB
JavaScript
import { $inject, $module, KIND, Primitive, createPrimitive } from "alepha";
import { AlephaDateTime, DateTimeProvider } from "alepha/datetime";
import { AlephaServer, ServerRouterProvider } from "alepha/server";
//#region ../../src/react/sitemap/primitives/$sitemap.ts
/**
* Expose a `sitemap.xml` generated from the application's `$page` primitives.
*
* Registers a `GET /sitemap.xml` route that reads every registered page at
* request time and emits a standard XML sitemap. Marked `static` by default, so
* the build prerenders it to `dist/public/sitemap.xml` for static deployments —
* while SSR runtimes also serve it live.
*
* The hostname comes from `options.hostname`, falling back to `PUBLIC_URL`, then
* to `""` (relative URLs).
*
* @example
* ```ts
* import { $sitemap } from "alepha/react/sitemap";
*
* class AppRouter {
* sitemap = $sitemap();
* }
* ```
*/
const $sitemap = (options = {}) => {
return createPrimitive(SitemapPrimitive, options);
};
var SitemapPrimitive = class extends Primitive {
router = $inject(ServerRouterProvider);
dateTime = $inject(DateTimeProvider);
onInit() {
this.router.createRoute({
method: "GET",
path: this.options.path ?? "/sitemap.xml",
static: this.options.static ?? true,
silent: true,
handler: (request) => {
request.reply.setHeader("content-type", "application/xml");
return this.buildSitemap();
}
});
}
/**
* Render the sitemap to its path and body. Used by the build to snapshot the
* sitemap to a static file.
*/
prerender() {
return {
path: this.options.path ?? "/sitemap.xml",
body: this.buildSitemap()
};
}
/**
* Build the sitemap XML from the application's page primitives.
*/
buildSitemap() {
const hostname = this.options.hostname ?? String(this.alepha.env.PUBLIC_URL ?? "");
const pages = this.getSitemapPages();
return this.generateSitemapFromPages(pages, hostname);
}
/**
* Select the pages that should appear in the sitemap.
*
* Excludes layout pages (with `children`), wildcard paths, and `/404`.
* Parameterized pages are included only when they declare `static.entries`.
*/
getSitemapPages() {
return this.alepha.primitives("page").filter((page) => {
const options = page.options;
const path = options.path ?? "";
if (options.children) return false;
if (path.includes("*")) return false;
if (path === "/404") return false;
if (!options.schema?.params) return true;
if (options.static && typeof options.static === "object" && options.static.entries) return true;
return false;
});
}
generateSitemapFromPages(pages, baseUrl) {
const urls = [];
const normalizedBaseUrl = baseUrl.replace(/\/$/, "");
for (const page of pages) {
const options = page.options;
if (!options.schema?.params) {
const path = options.path || "";
const url = `${normalizedBaseUrl}${path === "" ? "/" : path}`;
urls.push(url);
} else if (options.static && typeof options.static === "object" && options.static.entries) for (const entry of options.static.entries) {
const url = `${normalizedBaseUrl}${this.buildPathFromParams(options.path || "", entry.params || {})}`;
urls.push(url);
}
}
return this.buildSitemapXml(urls);
}
buildPathFromParams(pathPattern, params) {
let path = pathPattern;
for (const [key, value] of Object.entries(params)) path = path.replace(`:${key}`, String(value));
return path || "/";
}
buildSitemapXml(urls) {
const lastMod = this.dateTime.now().format("YYYY-MM-DD");
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map((url) => ` <url>\n <loc>${this.escapeXml(url)}</loc>\n <lastmod>${lastMod}</lastmod>\n </url>`).join("\n")}
</urlset>`;
}
escapeXml(str) {
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
}
};
$sitemap[KIND] = SitemapPrimitive;
//#endregion
//#region ../../src/react/sitemap/index.ts
/**
* Sitemap generation for React applications.
*
* Exposes the {@link $sitemap} primitive, which serves a `sitemap.xml` built
* from the app's `$page` primitives — live at request time and prerendered to a
* static file at build time.
*
* @module alepha.react.sitemap
*/
const AlephaReactSitemap = $module({
name: "alepha.react.sitemap",
imports: [AlephaServer, AlephaDateTime],
primitives: [$sitemap]
});
//#endregion
export { $sitemap, AlephaReactSitemap, SitemapPrimitive };
//# sourceMappingURL=index.js.map