Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions packages/reflex-base/src/reflex_base/plugins/sitemap.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import datetime
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path
from types import SimpleNamespace
from typing import TYPE_CHECKING, Literal, TypedDict
Expand All @@ -16,6 +17,8 @@
if TYPE_CHECKING:
from reflex.app import UnevaluatedPage

TrailingSlashOption = Literal["always", "never", "preserve"]

Location = str
LastModified = datetime.datetime
ChangeFrequency = Literal[
Expand Down Expand Up @@ -49,20 +52,30 @@ class Constants(SimpleNamespace):


def configuration_with_loc(
*, config: SitemapLinkConfiguration, deploy_url: str | None, loc: Location
*,
config: SitemapLinkConfiguration,
deploy_url: str | None,
loc: Location,
trailing_slash: TrailingSlashOption,
) -> SitemapLink:
"""Set the 'loc' field of the configuration.

Args:
config: The configuration dictionary.
deploy_url: The deployment URL, if any.
loc: The location to set.
trailing_slash: Option for handling trailing slashes in URLs.

Returns:
A SitemapLink dictionary with the 'loc' field set.
"""
if deploy_url and not loc.startswith("http://") and not loc.startswith("https://"):
loc = f"{deploy_url.rstrip('/')}/{loc.lstrip('/')}"
if trailing_slash == "always" and not loc.endswith("/"):
loc += "/"
elif trailing_slash == "never":
stripped = loc.rstrip("/")
loc = stripped or loc
link: SitemapLink = {"loc": loc}
if (lastmod := config.get("lastmod")) is not None:
link["lastmod"] = lastmod
Expand Down Expand Up @@ -121,11 +134,13 @@ def is_route_dynamic(route: str) -> bool:

def generate_links_for_sitemap(
unevaluated_pages: Sequence["UnevaluatedPage"],
trailing_slash: TrailingSlashOption,
) -> list[SitemapLink]:
"""Generate sitemap links from unevaluated pages.

Args:
unevaluated_pages: Sequence of unevaluated pages.
trailing_slash: Option for handling trailing slashes in URLs.

Returns:
A list of SitemapLink dictionaries.
Expand Down Expand Up @@ -159,52 +174,67 @@ def generate_links_for_sitemap(
continue

sitemap_link = configuration_with_loc(
config=sitemap_config, deploy_url=deploy_url, loc=loc
config=sitemap_config,
deploy_url=deploy_url,
loc=loc,
trailing_slash=trailing_slash,
)

elif (loc := sitemap_config.get("loc")) is not None:
sitemap_link = configuration_with_loc(
config=sitemap_config, deploy_url=deploy_url, loc=loc
config=sitemap_config,
deploy_url=deploy_url,
loc=loc,
trailing_slash=trailing_slash,
)

else:
loc = page.route if page.route != "index" else "/"
if not loc.startswith("/"):
loc = "/" + loc
sitemap_link = configuration_with_loc(
config=sitemap_config, deploy_url=deploy_url, loc=loc
config=sitemap_config,
deploy_url=deploy_url,
loc=loc,
trailing_slash=trailing_slash,
)

links.append(sitemap_link)
return links


def sitemap_task(unevaluated_pages: Sequence["UnevaluatedPage"]) -> tuple[str, str]:
def sitemap_task(
unevaluated_pages: Sequence["UnevaluatedPage"], trailing_slash: TrailingSlashOption
) -> tuple[str, str]:
"""Task to generate the sitemap XML file.

Args:
unevaluated_pages: Sequence of unevaluated pages.
trailing_slash: Option for handling trailing slashes in URLs.

Returns:
A tuple containing the file path and the generated XML content.
"""
return (
str(Constants.FILE_PATH),
generate_xml(generate_links_for_sitemap(unevaluated_pages)),
generate_xml(generate_links_for_sitemap(unevaluated_pages, trailing_slash)),
)


@dataclass(kw_only=True, frozen=True)
class SitemapPlugin(PluginBase):
"""Sitemap plugin for Reflex."""

trailing_slash: TrailingSlashOption = "preserve"

def pre_compile(self, **context):
"""Generate the sitemap XML file before compilation.

Args:
context: The context for the plugin.
"""
unevaluated_pages = context.get("unevaluated_pages", [])
context["add_save_task"](sitemap_task, unevaluated_pages)
context["add_save_task"](sitemap_task, unevaluated_pages, self.trailing_slash)


Plugin = SitemapPlugin
Loading
Loading