Know your tools
Something I appreciate doing here and there is building smaller scale projects, focused on going back to basics on technologies I use.
I had a few months of frenzy at work where everything we worked on was so comlicated (not complex, complicated) it felt like we couldn't build anything. To cope with that, I had fun doing small projects in less than an hour, usually in bed when I woke up. My Sunday morning hacks.
I think this is an important exercise, because it forces you to think about the tools and the trade. Especially in today's environment, so much tools are abstracting the trade, that we not only take them for granted, we forget they are even here to make things easier. Take a dev server for example. Whether it is a Rails, create-react-app or Next.js project, you simply start your dev server and magically have your templates compiled, your javascript transpiled, your assets served and maybe your browser automatically reloaded.
Have you ever thought about this? How it works and what is happening? I don't do it often, and that's why I think those little projects are important.
I did just that recently, as I built the website for jon&jess.
Jess designed a website, and except for a contact form, everything was static. So I started to think about which solution would fit better.
I first thought about Jekyll. It's simple and easy. I like Markdown, but to me, it's great to write, not to structure webpages. I also really like the export feature of Nextjs. It exports your app in a static, browser-only package of html and javascript. I'm very quick with Rails, and could deploy something in minutes to Heroku. But for our simple website, all those solutions are an overkill.
So I decided to do as simple as I could. Some templates, some css, and maybe some javascript. It shouldn't be anything more.
When you do such small projects is when you finally see how much is done for you by tooling. When you go back to the roots per se.
Let's build some html
So let's say I want 4 pages. Index, About, Work and Contact. That's not a lot of pages and those pages are pretty basic. But how am I going to handle the layout? Do I copy/paste the header, nav, footer, meta tags, etc in every single page? No way, that would be too tedious, annoying and prone to errors.
That's when you start asking the right questions. How is html built? When using Rails, I usually use haml or erb. When using Express I use pug. With React we're writing jsx.
So here I am, even for the simplest of website, having to look into templating engines. I'll use haml, mostly because I like it.
So now that I want to use haml to generate some html, let's start with a simple example.
Let's generate the following index.html:
# index.html
<html>
<body>
<p>Welcome!</p>
</body>
</html>
First, we need to create the corresponding index.haml:
# index.haml
%html
%body
%p Welcome!
Then we can run haml index.haml index.html
to generate the file.
Ok, so we are generating html from haml, but the CLI is not very useful since we can't do much more than what we just did. Let's write a ruby file to programatically generate our html. This will allow us to eventually add variables, etc.
# build.rb
require 'haml'
# read haml and generate html from it
src = Haml::Engine.new(File.open('index.haml')
out = src.render
# write html to file
File.open('index.html', 'w') do |f|
f.write(out)
end
Ok, now we have a little bit of power in our hands. Let's create a common layout and update our index.haml to only have our page-related content.
# index.haml
%p Welcome!
# layout.haml
%html
%head
%title A great website!
%body
= yield
If you are familiar with Ruby's yield
, the layout shouldn't bug your mind too much. If you are not, this will be clearer soon, but for now just know that in Ruby, methods can receive a block, and when they do, yield
is where the block gets called.
So when we do src.render
in our ruby file, we'll be able to add block as an argument, and its result will be inserted where yield
is.
So here the idea is that we'll render the layout.haml file, and pass it what we want as content in the yield.
# compile.rb
require 'haml'
# read layout's haml
layout = Haml::Engine.new(File.open('layout.haml')
# read index's haml
index = Hamle::Engine.new(File.open('index.haml')
# generate html by rendering layout and index inside layout's yield
out = layout.render do
index.render # this is the block
end
# write html to file
File.open('index.html', 'w') do |f|
f.write(out)
end
There. Now we can split our layout and our index, and when we run ruby compile.rb
, the correct html will get rendered.
Make the build easier
Now that we are able to compile our haml files into html, we need to make it a bit nicer. First, we'll have multiple haml files, not only the index. So we'll eventually want to go over all files in a views directory and build them.
Rebuilding every page for any change (on a single file) would not be very efficient, so we want to add a way to check and only compile what changed. Rake, a ruby-based task runner is perfect for that.
# Rakefile
require 'haml'
def compile target, src
layout = Haml::Engine.new(File.open('views/layout/layout.haml')
page = Hamle::Engine.new(File.open(src)
out = layout.render do
page.render
end
File.open(target, 'w') do |f|
f.write(out)
end
end
FileList['views/*.haml'].each do |src|
target = src.gsub('views/', '').gsub('haml', 'html')
file target => src do |t|
compile target, src
end
task :compile => target
end
Here I would recommend to check Rake's documentation if you are not familiar with it, but basically we are doing the following:
- We define a
compile
function which does exactly the same as what we did in compile.rb - For each haml file in views/, we create a file task
- We make the file task a dependency of the compile task.
Doing it this way allows for each haml file to have its own task, allowing Rake to only re-compile the haml file that changed instead of re-compiling everything each time.
Now we can run rake compile
and it will compile all files. When we update a file, we can run rake compile
again, and only the changed file will get re-compiled.
Let's automate our flow
Now we have nice little compiler for our haml, but it gets pretty annoying to have to run it manually everytime we do a change in the haml. Would be nice if it could get compiled automatically when a change is done.
Rake can't do that by itself, but another ruby utility called guard does exactly that, and has a specific rake module, making it very easy to use both together.
Guard requires a Guardfile to know what to watch for and what to do when an update happens. Pretty straight forward.
# Guardfile
guard 'rake', :task => 'compile' do
watch(%r{^views/.+\haml$})
end
This tells guard to run rake compile
when a haml file is updated in views/. Now we can run guard
and it will compile all our files, watch for changes, and recompile the correct file when one is updated.
And here we are, with a little, home made dev server. This is very similar (although simpler) than other dev servers coming with other framework.
The point of this is that knowing and mastering simple tools gives you a lot of power. We really want to build a simple website, and with the correct tools, we don't have to lean on bigger solutions that might be too big, or not perfectly adapted.
Knowing how the tools we use everyday work is very important exactly for this kind of reasons. Sometimes we need heavy lifting because we'll build a full web app, and a framework such as Rails is great. All the tooling and free stuff coming with it are really worth it.
But in this case, we just want to build a simple static website, with a few templates. No need for a big framework. Even a small framework (like jekyll etc) is too big. Being able to build our own mini-solutions is of great value!