GithubHelp home page GithubHelp logo

jpsca / jinjax Goto Github PK

View Code? Open in Web Editor NEW
254.0 254.0 14.0 3.9 MB

Server-Side Components with Jinja

Home Page: https://jinjax.scaletti.dev/

License: MIT License

Makefile 0.63% Python 97.59% Jinja 1.29% HTML 0.49%
component components jinja2 jsx-syntax python templates

jinjax's Introduction

🪐 Hi! I'm Juan Pablo

I develop open-source software, primarily Python tools and libraries, to satisfy my own needs, curiosity, and interests, but maybe you will find them useful too 😉

I am open to work, reach me out by email at [email protected] 📫 and on LinkedIn or Twitter/X

jinjax's People

Contributors

ahnafcodes avatar cemrehancavdar avatar jakobgm avatar jodal avatar jpsca avatar kamcio181 avatar mardiros avatar ramonvg avatar rix1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

jinjax's Issues

Django Setup?

Hi, are there any specific steps for installing to a django app?

How to work with Fastapi

@router.get('/article', summary='Article信息') async def get_article(id: int, db: AsyncSession = Depends(get_db)):

Jinjax will blocked when use async of fastapi?

maintenance status

Hi @jpsca

Thank you for providing this modern template engine. I appreciate using it in my current experimentation.

I've open two merge request recently. #48 amd #50 without any feedback.

I starts asking myself if you still maintain this project ?
Could you take the time to clarify the situation here.

As I see you are working on a pydantic library (fodantic) you may also be interested of my works here.
Note that I am currently using my fork due to CSRF tokens.

https://github.com/mardiros/fastlife/tree/main/tests/unittests/templating

Asset root path does not honor catalog folder root

The catalog folder set on Django is components-jx

    env.add_extension(jinjax.JinjaX)
    catalog = jinjax.Catalog(jinja_env=env)

    catalog.add_folder("components-jx")
    for dir in loader.searchpath:
        catalog.add_folder(os.path.join(dir, "components-jx"))

according to docs paths are resolved according to the folder root

The filepaths must be relative to the root of your components catalog (e.g.: components/).

however the generated imports are relative to 'components', no matter which root folder is set on jinja_env.py

image

component used, path <app>/<project>/jinja2/components-jx/Store/AddToCart.jinja

image

Render without parent component?

Hi! 👋 I think jinjax is something I've been looking for for a very long time. Now as I'm learning how to start with it, I figured out the starting point of rendering is that I tell the catalogue which top-level component I wish to render. But what if there's no top-level component? 🤔 My use cases would be:

  1. I'd like to drop components inside Markdown, effectively achieving something like MDX. The Markdown file lives e.g. in MkDocs and it's a plain Markdown text except of a few places where I want to include a component. I can pre-process such file with Jinja (MkDocs have a on_page_markdown hook), but I cannot give it a top-level component (perhaps I can, but it wouldn't be practical).
  2. I have a rather large project where I'd like to start using jinjax. It's a mess of Jinja macros and whatnot. There are, of course, no top-level components at the moment. I cannot go and rewrite everything, I need to roll the change gradually. I need the old code to live alongside the new code. In such environment, I need the standard template.render(**context) to be able to go through the Jinja code and process it as always, but also be able to process a component if it's present inside a normal Jinja code.

Is something like that possible, or are we talking about mutually exclusive worlds? Is it either all jinjax or nothing?

Performance impact

I love the cleanliness JinjaX, this is a great project.

However I wonder how much impact on performance it has ?

I did a very quick benchmark (you can run it from this colab notebook)

And here is the results :

  • JinaX, catalog.render : 423 µs ± 9.71 µs per loop
  • FastAPI Jinja2Templates : 44.3 µs ± 1.03 µs per loop

It's 10 times slower ? I mean, I'm not even sure this has an impact at all since the time is quite small in the grand scheme of things, but still...

Is there anyway to speed things up ?

Support for mounting React components

Thanks for creating the library! Is there any plan to integrate with React? I was thinking it would be a good alternative to MDX, where we can mount React components. MDX's control flow is based on JSX and as Jinjat already covers these use cases, Jinjax has the potential to fill the gap for engineers who know Jinja + Python building web apps.

attrs.render() attributes result it nested quoted string: p=""v""

Html attributes, collected on attrs like prop-a="hello" result in nested string quotes like prop-a=""hello hi"" when using attrs.render()

Server: Django 4

Jinja call site

<AddToCart prop-a="hello hi" prop-b="test"/>

generated html

<div class="flex items-center cursor-pointer h-[30px]"
     <!-- see double quotes-->
     prop-a=""hello hi""
     prop-b=""test""
>
    Add to cart
</div>

jinjax component

<div class="flex items-center cursor-pointer h-[30px]"
    {{ attrs.render() }}
>
    Add to cart
</div>

Update package 0.19 on pypi

The pypi package still has the print,s on jinjax.py (preprocess function)

        print("###############")
        print(source)
        print("###############")

Django

How to install it?
This is a honeypot issue, please ignore.

Jinjax does not work with flask-assets by default

Having code like this:

app = Flask(__name__)

assets = Environment(app)
css = Bundle("main.css", depends=["**/*.jinja"], output="dist/main.css", filters=[tailwindcss])

assets.register("css", css)

catalog = Catalog(
    globals=app.jinja_env.globals,
    filters=app.jinja_env.filters,
    tests=app.jinja_env.tests,
    extensions=list(app.jinja_env.extensions.keys())
)
catalog.add_folder("imgboard/static/components")

Will lead to flask-assets complaining about its environment being set up incorrectly. Adding the following line at the end of the above snippet fixes this issue:

catalog.jinja_env.assets_environment = app.jinja_env.assets_environment

It seems to me that the copying of the jinja env that happens when initializing a Catalog results in an incomplete copy, which leads to this issue with flask-assets but can also easily result in confusing errors when using other plugins.

Issue with rendering hx-on:click

Hello guys,

First of all thank you for the great library!!!

I've started rewriting my old UI components from raw jinja2 to jinjax and hit an issue.

I have component Button.jinja

{#def classes='' #}

<button class="{{ classes }}"
        {{ attrs.render() }}
>
    {{  content  }}
</button>

I want to use hx-on:click attribute on it (https://htmx.org/attributes/hx-on/).

Unfortunately calling

t = catalog.render("Buttons.Button", **{"__source": "<Buttons.Button hx-on:click=\"document.getElementById('input-file').click()\">Label</Buttons.Button>"})
print(t)

produces below - hx-on: is missing only click attribute is present

<button class=""
        click="document.getElementById('input-file').click()"
>
    Label
</button>

I've tried to debug it however I cannot find the place in code where attributes are split.

Could you take a look?

Edit: I was able to find the place in code and create a fix proposal.
Tests have passed but let me know if there is a better way for fixing it.
PR: #42

JinjaX does not work with autoescape mode

If autoescape is enabled, a subcomponent is rendered escaped.
This might be the result of a .strip() call.
Wrapping the result with Markup seems to work.

# component.py:

from markupsafe import Markup


class Component:
        def render(self, **kwargs):
            assert self.tmpl, f"Component {self.name} has no template"
            return Markup(self.tmpl.render(**kwargs).strip())


Append new attributes by default, add option to override behaviour

The attrs.render() usage is really powerful. It would be great if one could do something like this:

Component.jinja:

{#def type="info", hidden=false #}

{% do attrs.set(class=hidden) if hidden %}
{% do attrs.set(class="{{ type ~ '-modal'}}") %}

<div {{ attrs.render() }} class="modal">...</div>

Using the component:

<Component type="info" id="context" hidden />

My current solution is doing something like this:

{#def type="info", context="", hidden=false #}

<div {% if context %} id="{{ context }}" {% endif %}
     class="modal {{ type ~ '-modal' }} {{ 'hidden' if hidden }}">
   ...
</div>

Using the component:

<Component type="info" context="context" hidden />

Autorefresh not working properly

Autorefresh does not work with dependent .js and .css files. This bug was not so easy to see with #21 obscuring what was going on, but this is what I discovered:

Setup

Here I list the general specs of my setup. It should not matter much for this issue, so feel free to jump to What doesn't work. This is the relevant part of my application for reference:

catalog = Catalog(jinja_env=app.jinja_env)
catalog.add_folder("templates")
catalog.add_folder("templates/components"))
catalog.add_folder("static/assets/scripts"))
catalog.add_folder("static/assets/styles"))

app.wsgi_app = catalog.get_middleware(
    app.wsgi_app,
    autorefresh=app.debug
)

(The line catalog.add_folder("templates")is required to bypass issue #19)

  • I have most of my templates in the templates folder and all jinjax components in templates/components.
  • I have all scriptfiles and stylesheets in static/assets/scripts and static/assets/styles.
  • Importing .css and .js files works perfectly fine (except for #21). Everything is imported as it should be.
  • Updating the code of components works fine and displays the correct updates in the client.

What doesn't work

Updating the external (.css and .js) files required for a component does not work.

What I mean:

When updating the following import statement

{#js scripts/foo.js #}

to

{#js scripts/bar.js #}

the reference to scripts/foo.js stays in place and the following is rendered:

<script src="/static/components/scripts/foo.js" defer=""></script>
<script src="/static/components/scripts/bar.js" defer=""></script>

Documentation issue

In addition to #20, there is another issue with the documentation. This is not in the image, but on the page Adding CSS and JS | Documentation. Here you can find the instructions to add multiple external files (JavaScript and CSS) to the component:

{#css lorem.css ipsum.css #}
{#js foo.js bar.js #}

These do not work. I have tried this without success. If you include them as so, the following will be rendered:

<script src="/static/components/scripts/lorem.js ipsum.js" defer=""></script>
<script src="/static/components/foo.js bar.js" defer=""></script>

Correction

The correct way to import multiple files is by separating them using commas:

{#css lorem.css, ipsum.css #}
{#js foo.js, bar.js #}

What also doesn't work

The following code snippet also doesn't work, because only the first instances of js and css import statements are used:

{#css lorem.css #}
{#css ipsum.css #}
{#js foo.js #}
{#js bar.js #}

It'd be nice if could pass attrs from parent to child at compose time

Say I want a generic container component that can dynamically set args on declared child components

like this:

<Parent>
  <Child {someAttrsFromParent} />
  <Child />
</Parent>

You can do this w Jinja macros (awkwardly):

{% call(kwargs) parent() %}
  {{ child(foo=1, **kwargs) }}
  {{ child(foo=2, **kwargs) }}
{% endcall %}

where parent looks like this:

{% macro parent() %}
...
{{  caller( dict(bar=2) ) }}
...
{% endmacro %}

Jinjax fails with python3.9

The recent version (0.32) of jinjax fails with python3.9, even though pyproject.toml includes this version:

image

Slots

I have just discovered JinjaX. I really like the super clean syntax, and the ease of passing attributes to components. Awesome work!

A feature analogous to the Web Components' <slot> element could be an interesting addition.

Perhaps django-components's implementation can serve as inspiration. It supports conditional slots and nested slots.

Passing arguments and rendering wraps them in... one extra double quote in html?

I have observed a weird behavior.

<BaseLayout request={request}>
  <Card id="something" />
</BaseLayout>

Card.jinja:

{#def type="info", hidden=false #}

<div {{ attrs.render() }} class="{{ 'hidden' if hidden }}">
   ...
</div>

The result is this property in the html element:

id='"webgl-disabled-notice"'

No idea why. jinjax = "^0.31"
How i render templates:

from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
from jinjax.catalog import Catalog

_TEMPLATES_DIR = "src/templates"

templates = Jinja2Templates(
    directory=_TEMPLATES_DIR,
    # Whitespace control
    lstrip_blocks=True,
    trim_blocks=True
)

catalog = Catalog(jinja_env=templates.env)
catalog.add_folder(_TEMPLATES_DIR)
catalog.add_folder(f"{_TEMPLATES_DIR}/components")

def render(*args, **kwargs):
    return HTMLResponse(catalog.render(*args, **kwargs))

TemplateNotFound Exception and weired Text Error - BUG

Hey there!
I find your project very interesting and love the idea of it. Unfortunately when I try to use it, I run into a few issues.

  1. I get a random TemplateNotFound Exception. This happens with every template I have, but only when I try to load it for the second (or more) time. So whatever site I visit works without a problem, yet when I try to load a page I have visited before, it shows the error. This happens with all templates; not just the ones using components.
  2. The components are rendered as text instead of actual html components. Not sure what causes this. Looks similar like using .innerText instead of .innerHTML.

I do not know what causes these issues and I hope you can help me here. Anyways, I hope you continue this amazing project.

EDIT:

After some experimenting: Turns out, the TemplateNotFound Exception somehow has to do with the {{ catalog.render_assets() }}-statement. This was included in every page since it was part of the layout, even when there was no components to render. Now that I removed it, the error only shows up when I try to render a page with components and then any page.

Steps to Error:

  • open any pages without components; no matter how often -> works fine
  • open a page using components -> still works fine except for 2.
  • open any page (either reload or switch to any other page) -> TemplateNotFound Exception shows

About the error:

  • I am using flask
  • There are no exceptions before or after the TemplateNotFound Exception in the console.
  • When using {{ catalog.render_assets() }} in the Layout, the assets get linked and loaded correctly.
  • The error also occurs when {{ catalog.render_assets() }} is removed completely from everywhere.
  • When using {{ catalog.render_assets() }} everywhere, I can load every page exactly once; when not using {{ catalog.render_assets() }} or only using it on the page with the component, it behaves as described in Steps to Error

Edit 2:

updated title to ... - partially solved
Turns out this weired behaviour with the TemplateNotFound Exception only happens when: The folder with the components is not the same folder as the normal template folder.

More information on this:

I tried to recreate this exception on a blank flask project and failed at first. Only when I recreated the file structure of my original project I succeeded. My file structure looks something like this:

 - templates:
 -- index.html
 -- others.html
 -- ... .html
 -- components:
 --- Layout.jinja
 --- Component.jinja
 --- Other.jinja

When including the directories with catalog.add_folder I only added the folders actually containing components and styles/scripts for the components (templates/components, static/components/styles, ...). I did not however include the normal templates (templates). Even though none of the normal templates used components, after first using a component on any template, all templates not registered with jinjax became globally unavailable.

Steps to recreate:

  1. Create a blank flask project with the default project structure
  2. Create some jinja2 templates (without using jinjax)
  3. Add a folder for components and fill it with at least one jinjax component
  4. register the jinjax component folder with the catalog, but not the templates folder
  5. create a template using the component
  6. start the app
  7. navigate between the pages not using jinjax components without problems
  8. open a page using jinjax components - still works
  9. try to open any other page in the templates folder - flask breaks

Edit 3:

Changed title to TemplateNotFound Exception and weired Text Error - BUG
I figured out the issue with The components are rendered as text... as well:
Turns out this happens when there is no content inside a Component. Either when using the self-closing Syntax (<Component/>) or when just leaving the content blank (<Component></Component>). This for some reason renders the component as text. It does not matter whether the content is actually used inside the component (with {{ content }}). Leaving a space between the open- and closing tags (content = " ") solves this issue.

Calling async function from template

Can we call an async function form a template?

If you have an async method

async def hi()-> str:
     return "hi user"

Then this renders nothing on a component

The returned value is {{hi()}}

But if the method is not async it renders well.

Ability to customize <script> type

Currently injected scripts have the type="module" which always implies the defer attribute, this can lead to different execution order of scripts in relation with non-deferred scripts, which is not always desirable, and can lead to some scripts failing, it's needless to say that if all scripts are deferred this is not a problem

defer from MDN

This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded.

Scripts with the defer attribute will prevent the DOMContentLoaded event from firing until the script has loaded and finished evaluating.

Scripts with the defer attribute will execute in the order in which they appear in the document.

type="module" from MDN

This value causes the code to be treated as a JavaScript module. The processing of the script contents is deferred.

image

raise ComponentNotFound( jinjax.exceptions.ComponentNotFound: File with pattern `Unable to find a file named Home.jinja or one following the pattern Home.*.jinja` not found

When i try to follow the documentation exactly the same, i want to make sure that everything is working, but why i got this raise ComponentNotFound(
jinjax.exceptions.ComponentNotFound: File with pattern Unable to find a file named Home.jinja or one following the pattern Home.*.jinja not found?

My code, simple flask hello world app

image

the output is

image

Im new into the webdev with Flask.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.