Perfect CSS styles loading
I try to load CSS style in what seems to me the most optimized way possible with Jekyll
With a Liquid trick, I ask Jekyll to include only css blocs that are used in the page and to display them directly in the HEAD tag.
Problem
I want to be able to display the site as quickly as possible to the visitor without having to load useless files. Without having to ask the browser to parse CSS information that is useless for the current page (therefore no global file which contains all the styles of the site). I try to give the visitor only what they need when they need it to view the current page.
Keep in mind that parsing and applying CSS styles requires resources and memory from the browser. The more we send it, the longer it takes for slow and / or old devices.
For more accessibility, I want my HTML document to be W3C validated. CSS styles must therefore be placed in a STYLE
tag, in the HEAD
tag
Everything must be minified and uncommented to gain a few extra kb.
The solution must be scalable. I need to be able to have as many templates, layouts and stylesheets as I want.
I am using Jekyll, I must be able to do this easily using Liquid tags.
In short, a HTML file which contains the HTML content as well as the necessary styles placed in the HEAD tag. So that the CSS content is gzipped by the web server along with the rest of the HTML file. We end up with very little data to transfer.
My solution
File structure
To make it more convenient to me, I create a common.html
file which contains the setup of my html pages (from <html>
to </html>
) into which the layout content will be inserted. And in the same way, I created a common.css
file which will contain the CSS used for all pages of the website.
If I have an html file that I may include in my content at different places (in a layout, or directly in a post), then I create a CSS file of the same name. Example: video.html
, video.css
For each of my templates, I created a CSS file of the same name (if there is a need to associate an independent CSS to it). Example: post.css
, posts.css
It looks like this:
_includes/
common.html
common.css
post.css
posts.css
video.html
video.css
_layouts/
default.html
post.html
posts.css
Layouts
In my layout, the first thing I do is capturing the CSS styles of the current layout in a generated_css
variable:
<!-- _layouts/post.html (1/3) -->
{% capture generated_css %}
{{ generated_css }}
{% include posts.css %}
{% endcapture %}
(I concatenate a possible existing generated_css
variable to allow to add all the styles used in the page. Here it won’t be useful, but I use the same formula everywhere)
Then I capture all of my content in a generated_content
variable:
<!-- _layouts/post.html (2/3) -->
{% capture generated_content %}
<section class="post">
[...]
</section>
{% endcapture %}
… after which, I include my common.html
file
<!-- _layouts/post.html (3/3) -->
{% include common.html %}
My common.html
file will then receive the two variables generated_css
and generated_content
. He will just have to place them in the right place on the page:
<!-- _include_/common.html -->
<html>
<head>
<style>
{{ generated_css }}
</style>
</head>
<body>
{{ generated_content }}
</body>
</html>
Posts and pages
In terms of posts and pages, it’s a little different. If I create a variable generated_css
directly in the content, it will not be passed to the layout. So I have to put the information in the configuration. For example, for this page, I’m loading CSS styling which allows me to display code blocks with syntax highlighting. So I add the YML variable contains_code
like this:
‑‑‑
# 2021-09-07-my-blog-post.md
layout: post
contains_code: true
‑‑‑
Hello world :
```
<b>Hello world</b> is bold
```
… and in my common.html
file, I can then indicate that if contains_code
is true
, then I load the CSS style for the syntax highlighting of the code (the file _includes/code.css
for example).
<!-- _include_/common.html -->
{% if page.contains_code == true %}
{% capture generated_css %}
{{ generated_css }}
{% include code.css %}
{% endcapture %}
{% endif %}
<html>
[...]
</html>
Even more expandable …
The solution above works fine. On the other hand, it is quite difficult to extend. I have to add a condition in common.html
for every possible stylesheet.
To have a more extensible result, I can add an html tag directly in the MarkDown content of my post which looks like this:
<!--INCLUDE_CSS code.css END_INCLUDE_CSS-->
And then, in my common.html
file, I can ask it to find all of these tags that are in the content of my page (in the generated_content
variable), to extract the name of the file to include, list them, remove duplicates and add their content to the generated_css
variable:
<!-- _include_/common.html -->
{% assign CSS_list = "" %}
{% assign CSS_blocs = generated_content | split: "<!--INCLUDE_CSS" %}
{% for CSS_bloc in CSS_blocs %}
{% assign CSS_bloc_name_array = CSS_bloc | split: "END_INCLUDE_CSS-->" %}
{% assign CSS_bloc_name_size = CSS_bloc_name | size %}
{% assign CSS_bloc_name = CSS_bloc_name_array[0] | strip %}
{% if CSS_bloc_name_size > 1%}
{% assign CSS_list = CSS_list | append: "," | append: CSS_bloc_name %}
{% endif %}
{% endfor %}
{% assign CSS_list = CSS_list | split: "," | uniq %}
{% for CSS_inc in CSS_list %}
{% if CSS_inc !="" %}
{% capture generated_css %}
{{ generated_css }}
{% include {{ CSS_inc }} %}
{% endcapture %}
{% endif %}
{% endfor %}
<html>
[...]
</html>
This time, it kinda looks nice. We just have to minify it. With Jekyll, I use the jekyll-minifier
plugin with the option compress_css: true
and remove_comments: true
. This way I end up with a well-filled, well-minified <style>
tag. And my personal tags <!-- INCLUDE_CSS code.css END_INCLUDE_CSS -->
disappear from the code in production.
Collateral benefit
I can add Liquid tags in my stylesheets. For example, I define a color in my _config.yml
file and use it in my CSS code:
a{
color:{{ site.color }};
}
Other methods
I think it is possible to do this with SASS, but I don’t know how to use it very well.
Well I’m pretty happy with the result !
Hope it helps !