Open JuroOravec opened 2 weeks ago
To complicate things, I think many will put component_dependencies_js in the footer of the page (where it doesn’t block rendering), and the css-version in the head.
I’m wondering if we’re mixing two concepts here? “Where do I want my link tag” vs “I want to dynamically load my dependencies”.
Is adding a new tag another option?
about the track_dependencies-function, where would the user put it?
To complicate things, I think many will put component_dependencies_js in the footer of the page (where it doesn’t block rendering), and the css-version in the head.
Hm, ok, so in that case the point 1. won't work very well from UX perspective.
I’m wondering if we’re mixing two concepts here? “Where do I want my link tag” vs “I want to dynamically load my dependencies”.
Yeah. Btw, by "dynamic loading", we mean the feature that only JS/CSS dependencies of used components are used, right?
Is adding a new tag another option?
I'm thinking of going in opposite direction, unifying all the dependency-rendering logic under a single tag, e.g.:
Using type
kwarg to specify whether to render JS/CSS/both:
{% component_dependencies %}
{% component_dependencies type="js" %}
{% component_dependencies type="css" %}
Using dynamic=True/False
to decide whether to render ALL registered components (False
) or only those used (True
)
{% component_dependencies %}
{% component_dependencies dynamic=False %}
{% component_dependencies dynamic=True %}
about the track_dependencies-function, where would the user put it?
At the place where they initialize the template rendering. For example if I have a view that returns a rendered template, I would use it like so:
from django.shortcuts import render
def my_view(request):
data = do_something()
...
return track_dependencies(
context,
lambda ctx: render(request, "my_template.html", ...)
)
Altho, in this example, there's a question of what is the context
- a dict or a Context?
What could work better would be if we defined our own render
function. Which could work similarly to Django's render
, and would hide the logic of track_dependencies
. So we would have
from django_components import render
def my_view(request):
data = do_something()
...
return render(request, "my_template.html", {"some": "data"})
This, supplying our own render
(and render_to_string
) functions, is also what I do in django-components fork.
Two more things:
So in the discussion above I assume that {% component_dependencies %}
renders JS/CSS of ALL components. Because of tag name, it could also suggest that it would only render dependencies of a SINGLE (current) component (AKA "render dependencies of a component" instead of "render component dependencies"). Are you aware of people wanting to render JS/CSS like this, meaning handling JS/CSS per component, instead of grouping them all in a single place? From web development perspective, it makes more sense to group it all.
One extra idea when it comes to rendering dependencies - mostly just throwing it out there so I won't forget, as it probably deserves it's own discussion. (But it's useful to have it mentioned for when thinking about how to design the interface): Currently the idea is to render a single JS/CSS file per component class. We could add a field on the Component class to configure whether to render the dependency file once per class, or once for each render instance. But to allow the latter, we would need to allow users to differenciate between the rendered instances, so we would need to render the JS/CSS deps at the same time (meaning with the same Context) as we use to render the component's HTML. In other words we would allow to use Django templating syntax inside JS and CSS files.
I’m wondering if we’re mixing two concepts here? “Where do I want my link tag” vs “I want to dynamically load my dependencies”.
Yeah. Btw, by "dynamic loading", we mean the feature that only JS/CSS dependencies of used components are used, right?
Yes, "only load the dependencies of the components that are used on this page".
Is adding a new tag another option?
I'm thinking of going in opposite direction, unifying all the dependency-rendering logic under a single tag, e.g.:
* Using `type` kwarg to specify whether to render JS/CSS/both: * Render both JS and CSS: `{% component_dependencies %}` * Render only JS: `{% component_dependencies type="js" %}` * Render only CSS: `{% component_dependencies type="css" %}`
I like this, but not sure it's worth it because we would be breaking some existing code. Maybe keeping the old tags as reference to the new ones would work though.
* Using `dynamic=True/False` to decide whether to render ALL registered components (`False`) or only those used (`True`) * Render ALL components: `{% component_dependencies %}` * Render ALL components: `{% component_dependencies dynamic=False %}` * Render only used components: `{% component_dependencies dynamic=True %}`
The only reason you should render all components is if you somehow are expecting them to be in cache for the next page load. You're essentially trading first load time to speed up all successive page loads. This is likely not the right tradeoff for most people.
I like moving towards using this tag to only load used tags. Feels simple and clean.
about the track_dependencies-function, where would the user put it?
At the place where they initialize the template rendering. For example if I have a view that returns a rendered template, I would use it like so: (snip) What could work better would be if we defined our own
render
function. Which could work similarly to Django'srender
, and would hide the logic oftrack_dependencies
. So we would havefrom django_components import render def my_view(request): data = do_something() ... return render(request, "my_template.html", {"some": "data"})
Thanks for the explanation, makes sense!
The render method has a very wide surface area, that we then need to keep in sync with for new releases. Not sure I like that responsibilities. Also, overriding it won't work for generic views and other use-cases where you don't use the render function. I think I'm in favor of using component_dependencies
then.
Just to add something I've noticed with component_dependencies
+ the dynamic loading middleware. In cases where you dynamically replace content in a page (e.g., using hx-get
or hx-post
in HTMX), then the required JS and CSS aren't included.
So that means you have to render all the dependencies together. Or, at least, that's what I did when I had this issue. Not sure if we should consider this an issue because I guess it'd be hard to solve from django-components
side alone.
@dylanjcastillo So maybe there should be a view you can call to update the deps as well? Or to we include the new deps in the response from the server too?
Adding new deps sounds like a complex problem:
hx-get/post
could be made also from terminal with curl.My first thoughts:
?abc
) to specify what it needs.
Or, probably better approach, we could define a global JS variable that keeps track of all the loaded dependencies. And a function or two to CRUD and load the dependencies. If you ever hacked around Facebook or YouTube frontends in devtools, they do something similar to load only that code into the website that is requested.
So then, the user could access the data on which dependencies are loaded, and it would be up to them on how they want to send the data, whether through HTMX or something else. And they would also need to implement a Django endpoint that responds with the extra dependencies
Actually, maybe a better approach, so user doesn't have to do the heavy lifting -> We wrap all the JS / CSS dependencies in a JS script that calls the client-side JS library that manages the dependencies. If a new dependency was sent, it would be loaded onto the page. If it was loaded before, it will be ignored.
More thoughts - based on the discussion in https://github.com/EmilStenstrom/django-components/discussions/399, I'm thinking we could have 2 kinds of JS - class-wide and instance-specific. Class-wide is ran once when the component of certain class is first loaded. Instance-specific is ran each time the component is rendered. Class-wide would be like it is currently, meaning it does not have access to context and nor data from get_context_data
. On the other hand, the instance-specific JS would be rendered with the same Context as the HTML is.
Actually, maybe a better approach, so user doesn't have to do the heavy lifting -> We wrap all the JS / CSS dependencies in a JS script that calls the client-side JS library that manages the dependencies. If a new dependency was sent, it would be loaded onto the page. If it was loaded before, it will be ignored.
This sounds very interesting, but not sure what you mean by the "client-side JS library that manages the dependencies". Is that something we'd create?
In regards to the 2 kinds of JS, I agree. I often ended up doing some weird hacks with event listeners to simulate the Instance-specific one.
@dylanjcastillo Sorry for convoluted language, basically meant some JS script that would manage the dependencies. So yeah, we'd create/own that.
I think it makes sense to have a very lightweight JS script that tracks and updates dependencies makes sense.
I wonder if we could make the existing component views just pass their dependencies as HTTP headers? Then the JS could intercept those and update as needed.
Continuation from this comment https://github.com/EmilStenstrom/django-components/issues/277#issuecomment-2092358246
To refine the previous idea a bit more, I suggest following API for users to render dependencies:
Preferably, use
{% component_dependencies %}
(or the JS/CSS variants) in the top-level component BEFORE any other tags that could render components.Alternatively, instead of
{% component_dependencies %}
(or the JS/CSS variants) users may also usetrack_dependencies
for the same effect:If users need to place
{% component_dependencies %}
(or the JS/CSS variants) somewhere else than at the beginning of the top-level component, then we will need to the replacement strategy. I suggest to still use thetrack_dependencies
. Basically, if the template given totrack_dependencies
contains{% component_dependencies %}
tag, then we do the replacement strategy. If{% component_dependencies %}
is NOT present, then we first render the text and collect all dependencies, and prepend them to the rendered content.For users, the API would still be the same as in 2.:
And if users do not want to call
track_dependencies
for each template render, they can use the Middleware (as it is currently), with the caveat that it works only for non-streaming responses and of content typetext/html
.