Reducing CSS size by removing unused style class names. Objectos 0.5.2 released

Marcio Endo
March 26, 2023

Welcome to Objectos Weekly issue #019.

I have released Objectos 0.5.2! Like last week's release, this release contains almost no user facing changes; most of the changes were internal. Here are the list of changes.

In any case, I will show you what I have been working on.

Let's begin.

Before we begin

I use Objectos in production. This very page was generated using Objectos HTML (and a few other Objectos libraries).

Having said that, please know that Objectos is alpha software. In particular:

Reducing CSS size by removing unused style class names

Objectos HTML provides an API for consuming (or processing) a HTML template. Objectos HTML itself uses it to, for example, generate the formatted HTML file.

There are other use-cases for the API. You can use it to, for example:

With this information you can, e.g., reduce the size of your CSS by removing any unused style rule.

This is how this page is generated, in fact. This is also how the documentation pages are generated. Have a look at the GitHub repository.

Visiting all of the declared style attributes

As mentioned, Objectos HTML provides an API for processing a HTML template. I am currently working on making this API easier to use. So please be warned that there will be breaking changes, relative to the code shown here, in the upcoming releases.

The current API uses a "push" model. To consume a template, you implement a Visitor interface. Then all events, such as the start or end of a HTML element, are "pushed" to your visitor.

I am currently working to provide a (pseudo-)DOM API instead.

Next is an example on how to collect all of the distinct style class names in a HTML template. It uses the current "push" API:

import java.util.Set;
import java.util.TreeSet;
import objectos.html.tmpl.AttributeName;
import objectos.html.tmpl.StandardAttributeName;

public final class DistinctClassNames extends SimpleVisitor {
  private boolean collect;

  private final Set<String> names = new TreeSet<>();

  @Override
  public final void attribute(AttributeName name) {
    if (name == StandardAttributeName.CLASS) {
      collect = true;
    }
  }

  @Override
  public final void attributeFirstValue(String value) {
    if (collect) {
      names.add(value);
    }
  }

  @Override
  public final void attributeNextValue(String value) {
    attributeFirstValue(value);
  }

  @Override
  public final void attributeValueEnd() {
    collect = false;
  }

  @Override
  public final void documentEnd() {
    for (var name : names) {
      System.out.println(name);
    }
  }

  @Override
  public final void documentStart() {
    collect = false;

    names.clear();
  }
}

So, when the HTML document starts, we reset our state:

@Override
public final void documentStart() {
  collect = false;

  names.clear();
}

When we visit a class attribute, we enable the collecting:

@Override
public final void attribute(AttributeName name) {
  if (name == StandardAttributeName.CLASS) {
    collect = true;
  }
}

And we store all of the values of this particular attribute:

@Override
public final void attributeFirstValue(String value) {
  if (collect) {
    names.add(value);
  }
}

@Override
public final void attributeNextValue(String value) {
  attributeFirstValue(value);
}

We stop collecting when the attribute ends:

@Override
public final void attributeValueEnd() {
  collect = false;
}

We set collect to false regardless of its previous state.

Finally, when the document ends, we print all of the collected values:

@Override
public final void documentEnd() {
  for (var name : names) {
    System.out.println(name);
  }
}

As we are using a TreeSet the printed result will be sorted.

Multiple attribute values?

You may have noticed that, in the previous example, one can visit multiple attribute values.

Just know that, in Objectos HTML, declaring a div like so:

div(
  className("a"), 
  className("b"),
  h1("Hello world!")
);

Generates the following HTML:

<div class="a b">
<h1>Hello world!</h1>
</div>

And generates two 'attribute value' events to the visitor like so:

visitor.attribute(StandardAttributeName.CLASS);
visitor.attributeFirstValue("a");
visitor.attributeNextValue("b");
visitor.attributeValueEnd();

Using our visitor

Let's use our visitor in an example. Consider the following Objectos HTML template:

import objectos.html.HtmlTemplate;

public class Example extends HtmlTemplate {
  @Override
  protected final void definition() {
    doctype();
    html(
      lang("en"),
      className("no-js"),

      head(
        title("Objectos HTML example")
      ),
      body(
        h1(
          className("font-large"),
          className("font-sans"),
          className("text-bold"),
          t("Distinct class names")
        ),
        p(
          className("font-sans"),
          t("Some "),
          em(className("text-bold"), t("important")),
          t(" info")
        )
      )
    );
  }
}

It declares a few class attributes using the className instruction.

We can collect all of the distinct style class names using the following code:

public static void main(String... args) {
  var sink = new HtmlSink();

  var tmpl = new Example();

  var distinct = new DistinctClassNames();

  sink.toVisitor(tmpl, distinct);
}

When we run this program it prints:

font-large
font-sans
no-js
text-bold

Which are all of the distinct style class names in the HTML document.

Generating the reduced CSS

We now have all of the distinct style class names used in a particular HTML file. We can use this information to filter out style rules from the CSS file.

This blog and the Objectos documentation site uses Objectos CSS. It supports (or shall support) this use-case out-of-the-box.

Objectos CSS is not publicly released yet. But I expect to release it later this year. In the meantime, the source code is available at the GitHub incubator repository.

Until the next issue of Objectos Weekly

So that's it for today. I hope you enjoyed reading.

The source code of all of the examples are in this GitHub repository.

Please send me an e-mail if you have comments, questions or corrections regarding this post.