Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Review and fix Nunjucks HTML indentation #4448

Closed
wants to merge 25 commits into from

Conversation

colinrotherham
Copy link
Contributor

@colinrotherham colinrotherham commented Nov 9, 2023

This PR updates all Nunjucks templates with correctly indented HTML to close:

With two caveats:

  1. Textarea (and Character count) skips some indents to preserve <textarea> content
  2. Details component skips some indents to prevent extra spaces in code previews

Before

Checkboxes (without js-beautify formatter) from PR #4447
https://govuk-frontend-pr-4447.herokuapp.com/components/checkboxes

<div class="govuk-form-group">
  <div class="govuk-checkboxes"    data-module="govuk-checkboxes">
          <div class="govuk-checkboxes__item">
            <input class="govuk-checkboxes__input" id="nationality" name="nationality" type="checkbox" value="british">
            <label class="govuk-label govuk-checkboxes__label" for="nationality">
        British
      </label>
          </div>
          <div class="govuk-checkboxes__item">
            <input class="govuk-checkboxes__input" id="nationality-2" name="nationality" type="checkbox" value="irish">
            <label class="govuk-label govuk-checkboxes__label" for="nationality-2">
        Irish
      </label>
          </div>
          <div class="govuk-checkboxes__item">
            <input class="govuk-checkboxes__input" id="nationality-3" name="nationality" type="checkbox" value="other">
            <label class="govuk-label govuk-checkboxes__label" for="nationality-3">
        Citizen of another country
      </label>
          </div>
  </div>
</div>

After

Checkboxes (without js-beautify formatter) from this PR
https://govuk-frontend-pr-4448.herokuapp.com/components/checkboxes

<div class="govuk-form-group">
  <div class="govuk-checkboxes" data-module="govuk-checkboxes">
    <div class="govuk-checkboxes__item">
      <input class="govuk-checkboxes__input" id="nationality" name="nationality" type="checkbox" value="british">
      <label class="govuk-label govuk-checkboxes__label" for="nationality">
        British
      </label>
    </div>
    <div class="govuk-checkboxes__item">
      <input class="govuk-checkboxes__input" id="nationality-2" name="nationality" type="checkbox" value="irish">
      <label class="govuk-label govuk-checkboxes__label" for="nationality-2">
        Irish
      </label>
    </div>
    <div class="govuk-checkboxes__item">
      <input class="govuk-checkboxes__input" id="nationality-3" name="nationality" type="checkbox" value="other">
      <label class="govuk-label govuk-checkboxes__label" for="nationality-3">
        Citizen of another country
      </label>
    </div>
  </div>
</div>

Copy link

github-actions bot commented Nov 9, 2023

📋 Stats

File sizes

File Size
dist/govuk-frontend-development.min.css 112.45 KiB
dist/govuk-frontend-development.min.js 38.58 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 78.74 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 73.99 KiB
packages/govuk-frontend/dist/govuk/all.mjs 3.86 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend-component.mjs 359 B
packages/govuk-frontend/dist/govuk/govuk-frontend.min.css 112.44 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 38.57 KiB
packages/govuk-frontend/dist/govuk/i18n.mjs 5.38 KiB

Modules

File Size
all.mjs 70.32 KiB
components/accordion/accordion.mjs 21.67 KiB
components/button/button.mjs 4.7 KiB
components/character-count/character-count.mjs 21.24 KiB
components/checkboxes/checkboxes.mjs 5.83 KiB
components/error-summary/error-summary.mjs 6.57 KiB
components/exit-this-page/exit-this-page.mjs 16.08 KiB
components/header/header.mjs 4.46 KiB
components/notification-banner/notification-banner.mjs 4.93 KiB
components/radios/radios.mjs 4.83 KiB
components/skip-link/skip-link.mjs 4.39 KiB
components/tabs/tabs.mjs 10.16 KiB

View stats and visualisations on the review app


Action run for c6eca9c

@colinrotherham
Copy link
Contributor Author

colinrotherham commented Nov 9, 2023

Here's a diff of the HTML changes for all examples (bit big and repetitive) since #4416

@colinrotherham colinrotherham linked an issue Nov 9, 2023 that may be closed by this pull request
3 tasks
@owenatgov
Copy link
Contributor

I made a start on review and had began commenting on instances where the indent isn't respected for nunjucks functions like if and for blocks before I figured out that this changes enforces respect of html indentation but not nunjucks indentation. I'm feeling resistant to this as for me, it's going to make interpreting nunjucks code a lot trickier.

I there something we can do to clean up this extra indentation on render?

@colinrotherham
Copy link
Contributor Author

colinrotherham commented Nov 17, 2023

Thanks @owenatgov

You mean how I've moved away from indenting {% for %} and {% if %} for the sake of HTML?

Yeah I'd love to have an alternative too, it's definitely a compromise

I'll show a few of the options I tried here

Indenting both Nunjucks tags and HTML

This is what we mostly do currently, but indenting each Nunjucks tag level effectively double-indents the HTML

Input

{% if items | length %}
  <ul>
    {% for item in items %}
      <li>
        {% if item.active %}
          <a class="active">{{ item.text }}</a>
        {% else %}
          <a>{{ item.text }}</a>
        {% endif %}
      </li>
    {% endfor %}
  </ul>
{% endif %}

Output

The wrapping {% if items | length %} leaves the HTML indented by 2-spaces unnecessarily

Plus each Nunjucks tag level adds another 2-space indent

  <ul>
      <li>
          <a>One</a>
      </li>
      <li>
          <a class="active">Two</a>
      </li>
  </ul>

Indenting Nunjucks tags only

This is what I've tried to do as compromise, i.e. taking Nunjucks tags back an indent level alongside the HTML

You'll notice in lots of places though that we nest so deeply it's not possible

Input

{% if items | length %}
<ul>
  {% for item in items %}
  <li>
    {% if item.active %}
    <a class="active">{{ item.text }}</a>
    {% else %}
    <a>{{ item.text }}</a>
    {% endif %}
  </li>
  {% endfor %}
</ul>
{% endif %}

Output

HTML output is correctly 2-space indented

<ul>
  <li>
    <a>One</a>
  </li>
  <li>
    <a class="active">Two</a>
  </li>
</ul>

Indenting Nunjucks tags with indented macros

Alternatively, using the Summary list approach (with calls to _actionLink() and _summaryCard()) there's a third solution using macros but we'd have to strictly control indentation at the point every macro was called

I definitely think this can get too complicated but can be used sparingly

When nesting too deeply we'd then need clever combinations of {{- and trim to discard the indent level, followed by indent(2, true) (using true to indent the first line), to restore the correct level 😣

Input

{#- Render `<ul>` elements -#}
{%- macro list(items) %}
  <ul>
    {% for item in items -%}
      {{- listItem(item) -}}
    {% endfor -%}
  </ul>
{% endmacro -%}

{#- Render `<li>` elements -#}
{%- macro listItem(item) %}
  <li>
    {{ listLink(item) | trim }}
  </li>
{% endmacro -%}

{#- Render `<a>` elements -#}
{%- macro listLink(item) -%}
  {% if item.active %}
    <a class="active">{{ item.text }}</a>
  {% else %}
    <a>{{ item.text }}</a>
  {% endif %}
{%- endmacro -%}

{% if items | length %}
  {{- list(items) | trim -}}
{% endif %}

Output

HTML output is correctly 2-space indented

<ul>
  <li>
    <a>One</a>
  </li>
  <li>
    <a class="active">Two</a>
  </li>
</ul>

@colinrotherham
Copy link
Contributor Author

I there something we can do to clean up this extra indentation on render?

Yeah we currently use js-beautify to format the Review app code examples

But it means we miss HTML formatting issues, because we see the formatted version not the Nunjucks output

I've turned the formatter off via c9f891b in this PR (see preview)

Base automatically changed from attributes-trim to main November 20, 2023 16:12
@colinrotherham
Copy link
Contributor Author

colinrotherham commented Nov 23, 2023

We had a dev-catch up this morning

We should still clean up the HTML but restore Nunjucks nesting for readability

I'll give this PR an update and move it back into draft for now

@colinrotherham colinrotherham marked this pull request as draft November 23, 2023 11:05
Copy link
Contributor

@owenatgov owenatgov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this Colin. I've left a bunch of "indent query"'s Where I wanna understand what the impact of retaining the nunjucks indent is to the HTML, if it becomes too wild etc. I've been a bit lazy in how I've identified these lacks of indents because of uncertainties in the problem space itself.

An aside: I'm personally warming to the idea of us authoring our HTML in a more considered way, however I think if our nunjucks indentation is wild enough in a given template that it'll cause problems for our html authoring then we need to:

  1. find ways to re-structure that given template to reduce the amount of Christmas tree code
  2. communicate somewhere other than in this PR why we're ignoring nunjucks indentation

Another aside: how difficult would it be do you reckon to write something that automatically re-authored our nunjucks in response to the html output? This is very much spitballing but I wonder if we could set rules for how much html deviates from ideal authorship/indentation and then a script would fix those instances for us. I'd love it if nunjucks automated this at the build phase but it looks like that's just not something nunjucks can do.

<p class="govuk-body">{{ item.content.text }}</p>
{% endif %}
</div>
{% if item %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we indent the nunjucks here or does it make the html too gross?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -9,15 +9,19 @@
{% set classNames = classNames + " govuk-breadcrumbs--collapse-on-mobile" %}
{% endif -%}

<div class="{{classNames}}"{% for attribute, value in params.attributes %} {{attribute}}="{{value}}"{% endfor %}>
<div class="{{ classNames }}" {%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
<ol class="govuk-breadcrumbs__list">
{% for item in params.items %}
{% if item.href %}
<li class="govuk-breadcrumbs__list-item">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent query here and on line 22

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

</svg>
{% endset %}
{% set classNames = classNames + " govuk-button--start" %}
{% set iconHtml -%}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent query

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

</div>
{% endif %}
{% endif %}
{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %} data-module="govuk-checkboxes">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent query. This one looks like it's going to be tricky so I anticipate being pushed back against.

</h{{ headingLevel }}>
{% endif %}
{% if params.actions.items.length %}
{% if params.actions.items.length == 1 %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ident query. I suspect this one is going to massively indent the summary card markup so I'm open to pushback.

{% endif %}
</div>
{%- set summaryList %}
<dl class="govuk-summary-list {%- if params.classes %} {{ params.classes }}{% endif %}" {%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent query. What's wrong with the minus on the end of the set statement above?

<dl class="govuk-summary-list {%- if params.classes %} {{ params.classes }}{% endif %}" {%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
{% for row in params.rows %}
{% if row %}
<div class="govuk-summary-list__row {%- if anyRowHasActions and not row.actions.items %} govuk-summary-list__row--no-actions{% endif %} {%- if row.classes %} {{ row.classes }}{% endif %}">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent query

{{ row.value.html | safe | trim | indent(6) if row.value.html else row.value.text }}
</dd>
{% if row.actions.items.length %}
<dd class="govuk-summary-list__actions {%- if row.actions.classes %} {{ row.actions.classes }}{% endif %}">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent query, same for lines 66 and 68

{% endfor %}
</tr>
{% for row in params.rows %}
{% if row %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent query

Copy link

github-actions bot commented Dec 1, 2023

Other changes to npm package

The diff could not be posted as a comment. You can download it from the workflow artifacts.


Action run for c6eca9c

@colinrotherham
Copy link
Contributor Author

Going to close this one in favour of smaller easier-to-review PRs

@RichardBradley
Copy link

See #4676 (comment)

I think app supplied html params ought to be excluded from auto-indenting

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Review use indent in our templates
4 participants