django-components: Deprecate `component_block` in favour of `component`

The only difference between component and component_block is that component doesn’t require an ending tag. But component_block is more popular (I think) and the structures you can build with it are much more complex. In those cases, it would be nicer (and clearer, and require less typing) if you could just use {% component_block %} all the time.

Idea: Require {% component %} to have an ending tag, and make both tags behave exactly the same. Deprecate component_block.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 3
  • Comments: 37 (23 by maintainers)

Commits related to this issue

Most upvoted comments

I’m still deciding how I feel about this. All in all I agree with the idea of designing for new users, or at least in such as way feels familiar for Django developers.

The benefit of going from two tags to a single tag is clear: it’s one less thing to learn.

By contrast, playing devil’s advocate, I can see a few issues with it:

  1. More typing in the case of simple, slotless components. I believe there are many such cases (such as in the ‘button’ example below). How would existing users feel about having to add extra closing tags all over the place for the exact same effect?

    For instance, this—

    {% component "button" label="sign up" id="btn-sign-up" level="primary" %}
    

    —becomes this:

    {% component "button" label="sign up" id="btn-sign-up" level="primary" %}{% endcomponent %}
    
  2. We now have a pair of opening and closing tags without any content in between. This is odd seeing as the very raison d’être of tag pairs is to surround other template content. At least, that’s the only use case you’ll encounter in the Django docs. Empty tag pairs are in a sense visual noise.

    One design I can think of that addresses this emptiness – at the cost of ergonomics – is to move component arguments/context vars into the block (just as ‘fill’ tags are now). Our example above, for example, would turn into something like this (with or without closing ‘arg’ tags).

    Either

    {% component "button" %}
        {% arg "label" %} sign up {% endarg %}
        {% arg "id" %} btn-sign-up {% endarg %}
        {% arg "level" %} {{ level }} {% endarg %} 
    {% endcomponent %}
    

    Or

    {% component "button" %}
       {% arg label="sign up" %}
       {% arg id="btn-sign-up" %}
       {% arg level="primary" %} 
    {% endcomponent %}
    

    Obviously, this isn’t much better in terms of the amount of typing required and the visual space taken up by the component. It’s probably in every respect worse. =p

As for a self-closing {% component ... / %} shorthand variant – an example of which might be this:

{% component "button" label="sign up" id="btn-sign-up" level="primary" / %}

– I see a few issues:

  1. In Django, tags are generally expected to function independently or in pairs but not both. A shorthand like the one above would disrupt that expectation.
  2. The self-closing marker, e.g. /, might not stand out enough, making it harder to parse templates that make use of both variants – kind of like reading nested bracketed expressions containing a missing closing bracket somewhere.

Nevertheless, I believe users will expect a more compact/shorthand version, since this is something that is supported by (among other)…

  • React
  • Vue
  • Svelte
  • Laravel Blade components
  • Ember.js components
  • the RoR framework ViewComponents

I’m not that fond of {% Box %} because it feels too magical. I care a lot about how people that have no idea what django-components is, react to seeing it for the first time. If there’s a component in the name, that gives a nice hint that probably they should look in the components dir. Which dir? Well, there’s a name there directly after the component name…

@EmilStenstrom Interesting idea in the sense of ‘one less tag to be aware of’. On the other hand, it’s less typing if you don’t need any of the slot-related features. I think it’s for this reason that, for example, Vue.js allows both open and self-closing variants of components.

Here’s an idea: let’s see how many hits we get for each of the tags in Github’s code search and treat that as an estimate of their relative popularity?

@timothyis That sounds a lot like Django Slippers! I think it’s a pretty neat design. What I like less about it is that behind the scenes it leads to a vast proliferation of dynamically created template tags.

I’ve been thinking more about this. I think we’re making things too hard on ourselves. I still think just having a component tag that requires a endcomponent tag is the best way forward. This is an easy fix, if we ignore backwards compatibility. And since this is pre-1.0 software, I think we can break compatibility. I think it’s worth it.

So. New plan:

  1. Make the component_block tag spit out an error saying that component should be used instead.
  2. Crash if the component tag is missing an end tag.
  3. Add a Release note saying that needs to change, preferably with regexps that can make the change.

Agreed that 1) sends mixed messages. So it’s out. That leaves 3) and 2).

The problem with 3) is that if you miss that there’s a new setting, you might not get a grace period at all. You just get the breakage, and no new features until we ship 1.0 instead.

That’s why I like 2): I breaks things right away, but then gives the user a simple workaround.

Design thoughts:

  • Since this is a django project, we can do a management command. So it could be “manage.py [command]”
  • Thinking of this as something that’s already included in core django, I would suggest “components” as a good general term that might fit well with [sessions] or [staticfiles] commands.
  • Maybe keeping it simple with just “manage.py components upgrade” could work? We are in our own namespace, so I think we can afford to keep it simple. Would work well with a later “components create” command.

So the final command would be “./manage.py components upgrade close-component-tags <glob pattern>”.

Thoughts?

Oof, this turned into quite the essay. ✍️ Apologies. TL;DR I vote component/endcomponent and nothing else.


This was already pretty clear, but thanks for the overview. Handy to have an example of everything in one place!

I have mixed feelings about this. On the one hand, I can definitely see where you’re coming from. On the other, I see a few potential issues.

Impressions during usage

To get a feel for the new tag style, I tried it out it in an artifically complex template with embedded components inside fill tags and slot tags, inside components etc., and with mixed closed and unclosed usages. My initial impressions:

  • Under the strict assumption that only fill tags are permitted within the body of a componentendcomponent tag pair, and a 1-token lookahead is applied, parsing should succeed without ambiguity.
  • With consistent indentation, ‘self-closing’/void (to use the proper HTML term) component tags feel quite readable. When various component tags are combined on a single line it’s harder to parse visually what belongs with what. Again, one can probably learn to scan ahead for ‘fill’ tags, although it should be noted that such context sensitivity is not a standard requirement for reading Django templates or well-formed HTML.
  • With both closed and self-closing usages in the same template, the following auto-formatters/linters have varying degrees of success: djlint shows no difference in behavior (which is to say, it indents tags no less strangely than before); djhtml now fails, probably because it assumes that tags are always either self-closing or open but not both; pycharm pro’s builtin formatter works fine (note: Django support is a pro feature).

Based on the exercise above, my conclusions would be that, yes, we can implement a single component tag; yes, one could get used to reading/writing templates with them, although I’m sorry to see default tags wiped off the table; and finally, crap, we’re going to have either open PRs with djhtml and other tools, or learn the hard way that there’s a good reason why tags that have dual open and self-closing usages are uncommon in Django – not to mention non-existent in HTML, and sort-of-maybe-possible in XML.

Potential issues

Abstractly, there are a few inter-related themes we should pay attention to:

  • Readability
  • User surprise (less is better)
  • Compatibility with other tools
  • Django idiom (important with regard to potential inclusion in the core)

Concretely, in addition to what I observe above, these are my concerns:

  • AFAIK in Django tags are assumed either to be self-closing/void or to come in open–close pairs, but not both.
    • As observed above, diverging from this may have ramifactions for the compatibility of certain dev tools, such as auto-formatters/linters (intercompatibility issue)
    • It increases user surprise.
    • It is less idiomatic.
    • Mixed usage negatively affects readability.
  • #231 cannot be implemented. That’s unfortunate as this is a good step toward feature parity with the templating engines of popular frameworks. (I’ll admit, this sort of thinking borders on cargo culting, but I think in general users are more likely to convert to a new tool when the new tool does at least as much as they’re used to being able to do.)
    • However, I can see a middle road that might alleviate this point: if a component declares only one slot, then permit nameless slot and fill tags.
      # Component template "A.html"
      <div>
      {% slot %}
      lorem ipsum
      {% endslot %}
      </div>
      
      # In use
      {% component "A" %}
        {% fill %}
        Hello world!
        {% endfill %}
      {% endcomponent "A" %}
      
  • Related to the previous point, but much weaker: by adding such strict requirements to component parsing, we might be painting ourselves into a corner -> less future-proof.

On backwards compatibility

This seems to be a running theme in this thread. Now, normally, I’d absolutely 100% agree with the necessity of backwards compatibility. We know better than to piss off our relatively small user base. However, in light of PR #221, big changes are coming: users are going to have to make substantial changes to their templates anyway. Under the present circumstances, I’d argue it’s worth biting the bullet just this once™️ if it means getting this right.

What now?

Unless someone can point to an example of a widely adopted Django tag that has dual open–void usages, I feel some reluctance about adopting the proposed design. I realize it might sound needlessly limiting to say Django tags must be restricted to one usage only, but I believe not doing so might break the assumptions most users have about how Django tags work. Having said that, this does indeed feel like our most backward compatible design as well as the most (IMO) aesthetically pleasing.

My vote would still going to having component be an open tag requiring a closing endcomponent tag and supporting the self-closing/void case by means of a / (forward slash) suffix. Unfortunately, this still breaks djhtml, which only looks at the tag + the presence of an ‘end’ prefix…

Which leads me to conclude, reluctantly, that the least surprising, most idiomatic, most intercompatible, most future-proof (for e.g. default fills), and potentially more readable option is simply to have component/endcomponent. Full stop.

I mean, I personally prefer this…

{% load component_tags %}
<div>
  {% component "comp_a" %}
    {% fill "header" %}
      <p>another gin</p>
      {% component "big_header" title="Mr Bones' Wild Ride" %}
        {% fill %}
          {% slot "intro_text" %}
            {% component "placeholder_text" text="Your text" %}{% endcomponent %}  {# DIFFERENCE HERE #}
          {% endslot %}
        {% endfill %}
      {% endcomponent %}
    {% endfill %}
  {% endcomponent %}
</div>

… over this…

{% load component_tags %}
<div>
  {% component "comp_a" %}
    {% fill "header" %}
      <p>another gin</p>
      {% component "big_header" title="Mr Bones' Wild Ride" %}
        {% fill %}
          {% slot "intro_text" %}
            {% component "placeholder_text" text="Your text" / %} {# DIFFERENCE HERE #}
          {% endslot %}
        {% endfill %}
      {% endcomponent %}
    {% endfill %}
  {% endcomponent %}
</div>

@timothyis I think you might be onto something. Here’s a summary of what we have so far:

symbol/kwd example effect
/ {% component "Heading1" text="Hello world" / %} Self-closing
end {% component "Heading1" text="Hello world" end %} Self-closing
with {% component "Heading1" text="Hello world" with %} Component ctxt w/ closing tag

@EmilStenstrom, seeing as your original idea was to make the open–close tag pair the base case (i.e. the first thing new users learn), I’m going to guess you’re less a fan of with?

I wonder if end might be more agreeable? e.g.,

{% component "button" label="sign up" id="btn-sign-up" level="primary" end %}

Since this follows the Django pattern of using only at least, so keywords are a thing.

And then this matches the wording/context of

{% component "button" %}
   <span>Hello</span>
{% endcomponent %}

This could also be the other way around (taking GitHub’s Primer example), and using a keyword when there is a block (https://github.com/primer/view_components/blob/main/app/components/primer/button_component.html.erb#L2), e.g.,

{% component "button" with %}
   <span>Hello</span>
{% endcomponent %}

And then the non-block variant without the with keyword would just stop parsing at the end of the tag.

To me, it’s more important that it’s understandable, especially for people seeing the code for the first time. Sure, I think we can add some shortcuts for advanced users, but I think the defaults should be understandable by any Django developer, I think that’s a great target to have in mind.

Is this really so bad? Much simpler not having to care about the two different tags.

{% component "Heading1" text="Hello world" %}{% endcomponent %}

{% component "Heading1" %}
	Hello <b>world</b>
{% endcomponent %}