Whateverthing, now with a taste of Tailwind
The new Whateverthing is here!
I've redesigned the site in TailwindCSS and I'm very happy with how it came out. The previous design, based on Foundation 3, was alright, but it's been increasingly hard to find docs for Foundation 3 since it was becoming so ancient.
What is Tailwind?
Tailwind is a utility-first CSS library from Adam Wathan. Where other CSS frameworks aim to provide intent-based abstractions of CSS styles, Tailwind essentially maps 1:1 to actual CSS directives. You can use this approach to build complex interfaces with simple, clear implementations of your design's components.
"If you're sick of fighting the framework, overriding unwanted styles, and battling specificity wars, Tailwind was made for you."
Read more at the Tailwind website.
What Came Before
Here are a couple of screenshots of what the site used to look like in Foundation 3:
The First Hurdle
The upgrade started out well. I went in and stripped out a whole bunch of Foundation-esque classes and divs, making room for being able to put in Tailwind-style utility classes.
Toward the end of the project, I hit a hurdle that I thought I had already solved. In fact, it was a hurdle that has vexed me many times before: Footers!
I wanted a page footer that would always be at the bottom, even if there was only a half-screen worth of information on the page. And, I wanted it so that the bottom-anchored footer wouldn't stick to the bottom of the viewport on mobile.
I had thought my implementation was working, but when I tested on an actual iPhone I found out that Firefox's "responsive mode" couldn't fully replicate the quirks of Mobile Safari rendering.
After a lot of searching, I asked my friend JF Godin for advice. He pointed me to a page of Flexbox resources, and from that I was able to find the exact solution I needed.
That solution turned out to be setting the body
to flex flex-col min-h-screen
, and then setting my main section of content to flex-1
. This turned the page into a series of vertically-arranged blocks & made the middle one expand to the full screen height.
For a dedicated explanation of it, check out this helpful blog post from Philip Walton.
The Second Hurdle
After I got everything working nicely, I wanted to ensure that Webpack Encore's prod
mode would work within Sculpin. Sculpin is the static site generator I use for rendering the Whateverthing site. I use Webpack with the Encore add-on to generate the CSS and JS assets for the new design.
In prod
mode, Encore generates hashed filenames which allow for better cache-busting. They look something like /build/js/app.d49bfcca.js
. Unfortunately, because they aren't guessable, I can't hard code them into my Twig templates.
Luckily, part of the Encore output is a manifest.json
file that can map the dev
mode name (such as build/js/app.js
) to the hashed version.
I created my own Twig extension to read in the manifest.json
file and allow my CSS and JS to be included properly from Sculpin.
Creating a Twig extension in Sculpin isn't as easy as I would like it to be. I'll need to update the Sculpin docs to explain what I did so other folks can do it as well, but I'll write up a rough draft here.
Create a Class
In app/src/EncoreHelper.php
, I created a PHP class that acts as a Twig extension using the Twig GlobalsInterface
. It looked like this:
<?php
namespace Beryllium\Whateverthing;
use Twig\Extension\AbstractExtension;
use Twig\Extension\GlobalsInterface;
class EncoreHelper extends AbstractExtension implements GlobalsInterface
{
protected $sourceDir;
protected $manifest;
public function __construct(string $sourceDir, string $manifest) {
$this->sourceDir = $sourceDir;
$this->manifest = $manifest;
}
public function getGlobals()
{
$manifestContents = file_get_contents($this->sourceDir . DIRECTORY_SEPARATOR . $this->manifest);
if (!$manifestContents) {
throw new \RuntimeException('Webpack Encore manifest file was not found');
}
$manifest = json_decode($manifestContents, JSON_OBJECT_AS_ARRAY);
return [
'webpack_manifest' => $manifest,
];
}
}
This class sets a Twig global called webpack_manifest
that contains the
contents of manifest.json
as an array.
Load the Class
For loading the class in a way that Sculpin could see it, I modified my composer.json
to declare a PSR-4 autoloader for classes in app/src/
. If you want to bypass this part, you can probably get away with adding this line to the app/SculpinKernel.php
file:
require_once __DIR__ . '/src/EncoreHelper.php';
If you want to do the full Composer autoloader, you can add these lines & run composer update
:
"autoload": {
"psr-4": {
"Beryllium\\Whateverthing\\": "app/src/"
}
}
Configure the Extension
In your app/config/sculpin_kernel.yml
file, you can configure Symfony services
and tag them for the Symfony event dispatcher, which Sculpin uses as a backbone
for static site generation.
Configuring a service looks like this:
services:
custom.encore:
class: Beryllium\Whateverthing\EncoreHelper
arguments:
$sourceDir: '%sculpin.source_dir%'
$manifest: 'build/manifest.json'
tags:
- { name: twig.extension }
The arguments probably don't need to have the variable as the name, but I like to keep them there for clarity.
Anyway, at this point the extension should be registered and the webpack_manifest
global should be accessible by all of your Twig templates.
Using the Extension
When I updated my <link>
and <script>
tags to support this extension, I
ended up with this:
<link rel="stylesheet"
href="{{ webpack_manifest['build/css/app.css'] }}"
/>
<script type="text/javascript"
src="{{ webpack_manifest['build/js/app.js'] }}"
></script>
Using the key of build/css/app.css
, Twig looks into the webpack_manifest
array and comes out with the path to the generated assets. When in production mode, this will look something like /build/css/app.d49bfcca.css
.
Thanks!
Thanks for reading!
Please subscribe to the RSS feed to keep up to date, and definitely subscribe to my YouTube channel if you're interested in that sort of thing.
Published: March 7, 2020
Tags: dev, development, news, sculpin, twig, design, tailwind