Skip to main content

Jinja2 preprocessor with JSX-like syntax for FastAPI + HTMX + Stimulus

Project description

PJX

Jinja2 preprocessor with JSX-like syntax for FastAPI + HTMX + Stimulus.

PJX extends Jinja2 with components, control flow, frontmatter, conditional attributes, and template expressions that compile transparently to standard Jinja2 at template load time. No build step, no runtime overhead.

Installation

pip install pjx
pip install pjx[htmx]
pip install pjx[htmx,stimulus]
pip install pjx[all]

Run the CLI without installing the package globally:

uvx pjx --help
uvx pjx check templates/
uvx pjx check templates/ --fix
uvx pjx skills --claude
uvx pjx assets build static/vendor/pjx
uvx pjx assets add alpinejs@3 --dist alpinejs/dist/cdn.min.js --out js/alpine.min.js
uvx pjx assets list

Quick Setup

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from jinja2 import FileSystemLoader
from pydantic import BaseModel

from pjx import PJXEnvironment
from pjx.router import PJXRouter

app = FastAPI()
templates = Jinja2Templates(
    env=PJXEnvironment(loader=FileSystemLoader("templates"))
)
ui = PJXRouter(templates)
app.include_router(ui)


class HomeProps(BaseModel):
    title: str = "Hello"
    items: list[str] = []


@ui.page("/", "pages/home.jinja")
async def home(request: Request) -> HomeProps:
    return HomeProps(items=["Alice", "Bob", "Charlie"])

Example Template

---
from ..layouts import BaseLayout
from ..components import UserCard

props:
  title: str = "Dashboard"
  users: list = []

computed:
  has_users: props.users | length > 0
---

<BaseLayout title={props.title}>
  <h1>{{ props.title }}</h1>

  <div ?hidden={not has_users}>
    <For each={props.users} as="user">
      <UserCard name={user.name} />
    </For>
  </div>

  <Show when={not has_users}>
    <p>No users yet.</p>
  </Show>
</BaseLayout>

What You Get

  • JSX-like components in Jinja2 templates
  • <For>, <Show>, <Switch>, and <Fragment>
  • props:, vars:, computed:, and slots in frontmatter
  • Conditional attributes like ?hidden={expr}
  • HTMX and SSE aliases via pjx[htmx]
  • Stimulus aliases via pjx[stimulus]
  • cn() Tailwind class merging via pjx[tailwind]
  • FastAPI helpers via PJXRouter
  • CLI commands for template validation, formatting, and sitemap generation

Extension System

PJX uses a unified extension model. Each extension subclasses PJXExtension and can provide processors, Jinja2 globals, and browser asset providers through three hooks:

from pjx.extension import PJXExtension

class MyExtension(PJXExtension):
    @property
    def name(self) -> str:
        return "my-ext"

    def get_processors(self):
        return [(40, MyProcessor())]

    def get_jinja_globals(self):
        return {"my_func": my_func}

    def get_asset_provider(self):
        return MyAssetProvider()

Register extensions explicitly or let them be discovered automatically:

from pjx import PJXEnvironment

# Explicit registration
env = PJXEnvironment(
    loader=FileSystemLoader("templates"),
    extensions=[MyExtension()],
)

# Auto-discovery via pjx.extensions entry point
env = PJXEnvironment(loader=FileSystemLoader("templates"))

Third-party packages register extensions in pyproject.toml:

[project.entry-points."pjx.extensions"]
my-ext = "my_package.extension:MyExtension"

The old pjx.processors, pjx.jinja_globals, and pjx.assets entry point groups no longer exist. Use pjx.extensions instead.

Browser Assets

Installed extensions can provide browser assets that PJX injects automatically on full HTML documents:

  • pjx-htmx injects HTMX when the rendered HTML contains hx-* or sse-*
  • pjx-stimulus injects Stimulus when Stimulus data-* attributes are present
  • pjx-tailwind injects the Tailwind browser build when it detects utility classes or text/tailwindcss

CDN mode works out of the box:

templates = Jinja2Templates(
    env=PJXEnvironment(loader=FileSystemLoader("templates"))
)

Vendor mode switches injected URLs to local static files:

templates = Jinja2Templates(
    env=PJXEnvironment(
        loader=FileSystemLoader("templates"),
        asset_mode="vendor",
        asset_base_url="/static/vendor/pjx",
    )
)

Build vendor files with the CLI. The build generates a package.json, runs npm install, and copies dist files into the output directory. The resulting package.json and package-lock.json are kept for reproducibility:

pjx assets build static/vendor/pjx
pjx assets build static/vendor/pjx --provider htmx --provider stimulus

CLI

pjx check templates/
pjx check templates/ --fix
pjx format templates/
pjx sitemap templates/ --base-url https://example.com
pjx skills --claude
pjx skills --agents
pjx assets build static/vendor/pjx
pjx assets add alpinejs@3 --dist alpinejs/dist/cdn.min.js --out js/alpine.min.js
pjx assets list
pjx assets remove alpinejs
pjx demo

# no global install needed
uvx pjx --help
uvx pjx demo

pjx check --fix is for safe technical autofixes. pjx format stays focused on formatting and frontmatter layout. pjx assets build vendors browser assets via npm, merging extension and manifest packages. pjx assets add adds an npm package to the local pjx-assets.json manifest. pjx assets list shows all assets from extensions and the manifest. pjx assets remove removes a package from the manifest. pjx demo launches the bundled demo application.

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pjx-0.2.2.tar.gz (155.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pjx-0.2.2-py3-none-any.whl (163.6 kB view details)

Uploaded Python 3

File details

Details for the file pjx-0.2.2.tar.gz.

File metadata

  • Download URL: pjx-0.2.2.tar.gz
  • Upload date:
  • Size: 155.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pjx-0.2.2.tar.gz
Algorithm Hash digest
SHA256 b6abab5f566bc5cdf630a1f6ba09c798617aa4e62256a3e01cbd014ccf1dc767
MD5 4f7368c1ba7f889f95a8df7fd385b2ff
BLAKE2b-256 046ce8373e4c99bb410155defd5e4fabe10ca967bdda0ba0196e343f657f1419

See more details on using hashes here.

Provenance

The following attestation bundles were made for pjx-0.2.2.tar.gz:

Publisher: release.yml on oornnery/pjx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pjx-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: pjx-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 163.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pjx-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 3b10d6025737152fd88470cec2de9952399c430eeb397b8eb34abf61bd9d8f83
MD5 ee2ecbb6ee0225dfdb20dba772650b17
BLAKE2b-256 4fdfcafd9c68180b3d1eb68809325d18763a160cd83086b572790934a592070c

See more details on using hashes here.

Provenance

The following attestation bundles were made for pjx-0.2.2-py3-none-any.whl:

Publisher: release.yml on oornnery/pjx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page