JinjaX
repository·main·Indexed 19 days ago
https://github.com/jpsca/jinjaxA component-based framework for Python web applications (version 0.65) that allows developers to write server-side components as standalone Jinja templates and use them as native HTML tags. It features a central registry via jinjax.Catalog, support for component arguments using {#def #}, automatic CSS and JS asset discovery, and a WSGI middleware for serving assets.
What's inside jinjax
- JinjaX is a library for building server-side-rendered Python web applications using a component-based approach. It allows you to write server-side components as single Jinja template files and use them directly as HTML tags in your templates without explicit importing. This pattern aims to bring component-based clarity to Python web development.
Use prefixes to namespace components
mainThe
add_folder()method accepts an optionalprefixargument that acts as a namespace.- A component named
Card.jinjabecomescommon:Cardwhen added with the prefixcommon. - Subfolders are preserved:
wrappers/Card.jinjabecomescommon:wrappers.Card.
Component Resolution Rules:
- Prefix Precedence: Prefixes take precedence over subfolders. Do not create a subfolder with the same name as a prefix, as it will be ignored.
- Scoped Searching: When a component under a prefix calls another component without a prefix, JinjaX searches first under the caller's prefix and then under the empty prefix. This allows library components to work regardless of the prefix assigned to the library.
- Overriding: If multiple folders contain the same component under the same prefix, the folder added first takes precedence. You can use this to override library components by adding a local folder with the same prefix earlier in your setup.
# Example of adding a folder with a prefix catalog.add_folder("my_components", prefix="common")- A component named
Use implicit content slots in components
mainIn Jinjax, any content placed between the opening and closing tags of a component is automatically passed to that component as an implicit
contentvariable. This is known as a slot. The component is responsible for the outer structure, while the user provides the inner content.Example of a component definition:
<button class="fancy-btn"> {{ content }} </button>Example of using the component:
<FancyButton> <i class="icon"></i> Click me! </FancyButton><button class="fancy-btn"> {{ content }} </button>How component names are derived from subfolders
mainWhen a component is placed inside a subfolder, its name is constructed by joining the subfolder names and the filename with dots, converted to
PascalCase.- Structure:
components/Person/Form.jinja$\rightarrow$ Name:Person.Form - Structure:
components/password-reset/form.jinja$\rightarrow$ Name:PasswordReset.Form(Note: the kebab-case folder is converted to PascalCase).
You call these using the dot notation in both Python and HTML tags.
# Calling a nested component from Python catalog.render("Person.Form")<!-- Calling a nested component from another component --> <Person.Form> some content </Person.Form>- Structure:
Using Jinja macros as a foundation
mainJinja macros are template snippets that function similarly to Python functions. They accept positional or keyword arguments and return rendered text.
To pass child content (blocks of HTML) into a macro, you must use the
{% call %}syntax in conjunction with the{{ caller() }}function inside the macro definition. Without{% call %}, macros behave like standard functions.{# Macro definition #} {% macro button(type="button") -%} <button type="{{ type }}" class="btn-blue"> {{ caller() }} </button> {%- endmacro %} {# Usage with child content #} {% from 'forms.html' import button %} {% call button("submit") %}Submit{% endcall %}Use the `attrs` object for extra arguments
mainIf you pass arguments to a component that were not declared in its
{#def #}block, they are collected into a specialattrsobject. You can render these collected attributes onto an HTML element using{{ attrs.render() }}.Warning: You cannot pass
attrsto another component using<Component {{ attrs.render() }} />because components are translated to function calls before rendering. Instead, you must pass theattrsobject itself using the special_attrsargument.{# Correct way to pass extra attributes to a child component #} <MyButton _attrs={{ attrs }} /> <MyButton :_attrs="attrs" />Auto-loading CSS and JS assets in JinjaX
mainJinjaX provides automatic asset discovery based on component naming conventions. If a component file exists (e.g.,components/common/Form.jinja), JinjaX will automatically attempt to load a.cssfile and a.jsfile with the same name in the same directory (components/common/Form.cssandcomponents/common/Form.js). These assets are only added to the page if the files actually exist.Understanding the motivation for JinjaX
mainJinjaX is designed to bring the component-based development model (familiar to React or Vue users) to server-side rendered (SSR) applications using Jinja.
While standard Jinja templates often suffer from 'HTML soup' (long methods, deep nesting, and scattered variables), JinjaX aims to provide a way to organize markup, logic, and styles into modular, reusable packages. It seeks to bridge the gap between the power of Jinja macros and the ergonomic, JSX-like syntax of modern frontend frameworks.
Use index.jinja to simplify component names
mainIf a subfolder contains a file named
index.jinja, that file is treated as the primary component for that folder name. This allows you to call the component using only the folder name instead of the full path.Example Structure:
└ components/ └─ Tab/ ├─ index.jinja └─ Panel.jinjaTab/index.jinjais called asTab.Tab/Panel.jinjais called asTab.Panel.
# Using the index component catalog.render("Tab") # Using a sibling component in the same folder catalog.render("Tab.Panel")Organize components using subfolders
mainYou can organize components into nested directories. To reference a component located in a subfolder, use a dot (
.) between each directory name in the component call.For example, a component at
form/Button.jinjais called asform.Button, and a component atproduct/items/Header.jinjais called asproduct.items.Header.<form.Button> ... </form.Button> <product.items.Header> ... </product.items.Header>Integrate third-party component libraries
mainTo use components from an installed library, add the library's component path to your catalog using
catalog.add_folder(). The path provided by the library must be an absolute path.import jinjax_ui ... catalog.add_folder(jinjax_ui.components_path)Render components inside Jinja templates with irender()
mainTo use components inside a standard Jinja template, add the
cataloginstance to your Jinja environment's globals. Then, usecatalog.irender("ComponentName", ...)to render the component. This is useful for embedding small components like buttons or forms within larger templates.# Register catalog as a global in your Jinja environment app.jinja_env.globals["catalog"] = catalog{# Inside a template #} <div> {{ catalog.irender("LikeButton", title="Like and subscribe!", post=post) }} </div> <p>Lorem ipsum</p> {{ catalog.irender("CommentForm", post=post) }}