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.3.tar.gz (155.6 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.3-py3-none-any.whl (163.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pjx-0.2.3.tar.gz
  • Upload date:
  • Size: 155.6 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.3.tar.gz
Algorithm Hash digest
SHA256 019d5cdb09c0cb2e51db59664dfe7dea3ab21297a1dda08c6deed388c5d15d4e
MD5 afbd244abc587d2bf7f12b6fa938a3e5
BLAKE2b-256 32ccffa7e6836faf2174fc4a1810500243a0ce339378011b394b3f23544da03e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pjx-0.2.3.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.3-py3-none-any.whl.

File metadata

  • Download URL: pjx-0.2.3-py3-none-any.whl
  • Upload date:
  • Size: 163.7 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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 b9d4b6d573ebf59057400c6e5635c4585237ca9cc846fbf47a0d1b8ede614b4b
MD5 5f6dafe1da60c43e50ff9f49f442b390
BLAKE2b-256 52b0fde68e41208f8327a793831f1023b2d41ecaecb67137d88c856e64556686

See more details on using hashes here.

Provenance

The following attestation bundles were made for pjx-0.2.3-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