DEV Community

loading...

What Did Prettier Do to My HTML?

walkingriver profile image Michael D. Callaghan Originally published at walkingriver.com on ・5 min read

I have resisted installing and using Prettier for a long time now, mostly because I was happy enough with the job VS Code does formatting my code. Then over the Christmas break, my son convinced me to install it. After I did, he said, “Now open an HTML file and I’ll show you something you aren’t going to like.” Sure, now he tells me! That struck me as ominous, but I decided to go along for the ride.

We opened an HTML file and immediately reformatted the file using Prettier’s default settings. Near the top of the file was a block of HTML rendering an unordered list, representing a navigation menu.

<ul>
  <li>
    <a routerLink="/" (click)="closeSidebarPanel()">Home</a>
  </li>
  <li>
    <a routerLink="/reports/" (click)="closeSidebarPanel()">Reports</a>
  </li>
  <li>
    <a routerLink="/annual-sales/" (click)="closeSidebarPanel()">Annual Sales</a>
  </li>
  <li>
    <a routerLink="/product-maintenance/" (click)="closeSidebarPanel()">Product Maintenance</a>
  </li>
</ul>

Enter fullscreen mode Exit fullscreen mode

The last two lines with <a> tags are longer than 80 characters, which is Prettier’s default maximum line length. During a reformat to wrap those lines, Prettier needs to make a decision on how to do so without semantically affecting the rendered output. The decision most formatters would make might end up with a block of code like this.

<ul>
  <li>
    <a routerLink="/" (click)="closeSidebarPanel()">Home</a>
  </li>
  <li>
    <a routerLink="/reports/" (click)="closeSidebarPanel()">Reports</a>
  </li>
  <li>
    <a routerLink="/annual-sales/" (click)="closeSidebarPanel()">
    Annual Sales</a>
  </li>
  <li>
    <a routerLink="/product-maintenance/" (click)="closeSidebarPanel()">
    Product Maintenance</a>
  </li>
</ul>

Enter fullscreen mode Exit fullscreen mode

The problem with this is that the HTML <a> tag is an inline-display element, meaning it flows along with the text that’s around it. A code formatter has no way to know whether the whitespace it just added to the beginning of the line is significant. It is entirely possible, even likely, that it just inadvertently added an extra space to the menu label.

What does Prettier do instead? It tries to make an intelligent decision on how to wrap and reformat those lines based on an item’s CSS display rules. If the element is a block-level element, then adding space around it will not affect its layout at all. On the other hand, if the element is an inline-display element, it will take care not to add any additional whitespace. What it does instead is rather clever. This is the final output.

<ul>
  <li>
    <a routerLink="/" (click)="closeSidebarPanel()">Home</a>
  </li>
  <li>
    <a routerLink="/reports/" (click)="closeSidebarPanel()">Reports</a>
  </li>
  <li>
    <a routerLink="/annual-sales/" (click)="closeSidebarPanel()"
      >Annual Sales</a
    >
  </li>
  <li>
    <a routerLink="/product-maintenance/" (click)="closeSidebarPanel()"
      >Product Maintenance</a
    >
  </li>
</ul>

Enter fullscreen mode Exit fullscreen mode

The whitespace inside the tag itself, around its attributes, is never significant. Thus, it wraps the line between the final attribute and the angle bracket that closes the tag. That angle bracket is placed on the next line (remember, that whitespace is irrelevant), followed immediately by the text of the link itself. The link is then closed, again without any whitespace added to the text.

What about that closing angle bracket?

Why does it do this, though?

    <a routerLink="/product-maintenance/" (click)="closeSidebarPanel()"
      >Product Maintenance</a
    >

Enter fullscreen mode Exit fullscreen mode

And not this, which might be more intuitive?

    <a routerLink="/product-maintenance/" (click)="closeSidebarPanel()"
      >Product Maintenance</a>

Enter fullscreen mode Exit fullscreen mode

The reason, as I understand it, is that Prettier is intentionally placing the closing angle bracket on its own line, vertically lining it up with the tag’s opening angle bracket. In this way, it visually appears as a semantic block, exactly the way curly-brace languages line up their braces. Viewed from that perspective, I find it to be both elegant and clever.

My son was right. I did not like it when I first saw it, but with understanding comes appreciation and acceptance.

HTML Attribute Organization

Another change I did not expect is the way Prettier organizes HTML attributes when the tag has a lot of them. It formats them with one attribute per line, and closes the tag with the /> on the final line, as it did with the <a> tag above. I found a couple things to like about this.

It is really easy to find, edit, add, and remove attributes. This is particularly noticeable if you use a source control system such as Git (you do use source control, right???).

Consider the following HTML element definition:

<input #emailAddress type="email" class="email-input" placeholder="Email Address" required id="email-address" title="Enter your email address" name="email-Address" [(ngModel)]="emailAddress">

Enter fullscreen mode Exit fullscreen mode

That is quite the list of attributes, but it is by no means unusual. At first glance, is this element required? Does it have an id attribute? Finding the answers to those question is not difficult, but it does take a little effort to scan the attributes when they are organized that way.

This is how Prettier reformats that element.

<input
  #emailAddress
  type="email"
  class="email-input"
  placeholder="Email Address"
  required
  id="email-address"
  title="Enter your email address"
  name="email-Address"
  [(ngModel)]="emailAddress"
/>

Enter fullscreen mode Exit fullscreen mode

Notice that it did not change the order of the attributes. It honored my decision of where each belongs. Some formatters insist on alphabetizing them. That may even be a Prettier option, but as I said above, I am only working with its defaults for now. Maybe I will investigate and tweak it later, but one of its advantages is that you can use it “right out of the box,” as it were.

The final piece of brilliance with this formatting decision concerns source control, which I mentioned earlier. In the first element definition above, if I were to add a new attribute (say, disabled), the entire line would show as “changed” in the source control’s “diff” screen. Granted, finding such a small change would be reasonably simple and require very little mental effort.

Now imagine many such changes. Each one by itself is simple, but adding dozens or hundreds of these changes can start to feel like a burden.

Prettier’s decision of displaying one-attribute-per line means that each change affects exactly one line. Changes are easy to spot; they clearly stand out from the surrounding code, allowing you to see and understand what changed quickly and easily.

This advantage carries through to adding attributes right before the closing /> characters. Because a new attribute is on its own line, it will display as a single change in source control.

To me, this is a huge win.

Bottom Line

I will keep using Prettier, at least for my own projects. Next is getting my team at work to share in my appreciation.

The longer explanation of the whitespace decision can be found on the Prettier blog.

Discussion (0)

pic
Editor guide