JinjaX

repository·main·Indexed 19 days ago

https://github.com/jpsca/jinjax

A 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.

Tokens
11K
Snippets
47
Records
63
Agent score
65%

What's inside jinjax

  1. Overview of JinjaX

    main
    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.
  2. Use prefixes to namespace components

    main

    The add_folder() method accepts an optional prefix argument that acts as a namespace.

    • A component named Card.jinja becomes common:Card when added with the prefix common.
    • Subfolders are preserved: wrappers/Card.jinja becomes common:wrappers.Card.

    Component Resolution Rules:

    1. Prefix Precedence: Prefixes take precedence over subfolders. Do not create a subfolder with the same name as a prefix, as it will be ignored.
    2. 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.
    3. 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")
  3. Use implicit content slots in components

    main

    In Jinjax, any content placed between the opening and closing tags of a component is automatically passed to that component as an implicit content variable. 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>
  4. How component names are derived from subfolders

    main

    When 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>
  5. Using Jinja macros as a foundation

    main

    Jinja 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 %}
  6. Use the `attrs` object for extra arguments

    main

    If you pass arguments to a component that were not declared in its {#def #} block, they are collected into a special attrs object. You can render these collected attributes onto an HTML element using {{ attrs.render() }}.

    Warning: You cannot pass attrs to another component using <Component {{ attrs.render() }} /> because components are translated to function calls before rendering. Instead, you must pass the attrs object itself using the special _attrs argument.

    {# Correct way to pass extra attributes to a child component #}
    <MyButton _attrs={{ attrs }} />
    <MyButton :_attrs="attrs" />
  7. Auto-loading CSS and JS assets in JinjaX

    main
    JinjaX 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 .css file and a .js file with the same name in the same directory (components/common/Form.css and components/common/Form.js). These assets are only added to the page if the files actually exist.
  8. Understanding the motivation for JinjaX

    main

    JinjaX 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.

  9. Use index.jinja to simplify component names

    main

    If 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.jinja
    • Tab/index.jinja is called as Tab.
    • Tab/Panel.jinja is called as Tab.Panel.
    # Using the index component
    catalog.render("Tab")
    
    # Using a sibling component in the same folder
    catalog.render("Tab.Panel")
  10. Organize components using subfolders

    main

    You 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.jinja is called as form.Button, and a component at product/items/Header.jinja is called as product.items.Header.

    <form.Button> ... </form.Button>
    
    <product.items.Header> ... </product.items.Header>
  11. Render components inside Jinja templates with irender()

    main

    To use components inside a standard Jinja template, add the catalog instance to your Jinja environment's globals. Then, use catalog.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) }}