Part 3: Static Site Generator
To give you a sense of how all of these Ruby features come together, here is a realistic example of Ruby in action.
The Programming Languages course web site — the site you are looking at now! — is generated by a data-driven static site generator (SSG) I wrote called Superfluous, and Superfluous is written in Ruby. You are welcome to read through its source code if you want to see lots of Ruby in action, but that’s not at all necessary for this homework.
Long aside: What’s a static site generator?
Many web sites render each page dynamically: every time someone visits a page on the site, some code runs (maybe on a server, maybe in the browser, maybe both) to dynamically piece together all the parts of the page. That code is custom code, written for that specific web site. This approach opens up a lot of possibilities: the site can show something different depending on who’s asking and whether they’re logged in, can fetch the latest data from a database, can render user-generated content, and can even incorporate changing conditions like the current time or the current weather.
(Some examples of dynamic web site frameworks: Rails, Django, Laravel, Java Spring, Svelte, Vue, React, Wordpress.)
A static site, on the other hand, is completely pre-rendered before any requests for the pages. Typically that pre-rendered site is just a bunch of static files on a disk (“static” here meaning ”not code, just data”). There is a web server that dynamically handles each individual request for a page, but that web server just says “look for the file, return what’s there.” This is called a static web server. Such a web server is not website-specific custom code; it can be a general purpose piece of software such as nginx or Apache HTTP Server.
A static site can be completely hand-coded; you can manually type out .html and .css files in a text editor, then deploy them using one of those general purpose web servers. However, that ends up being repetitive: pages share structure; anything data-driven requires repetitive formatting. It’s nice to have code that generates all of the static files from some kind of template. That’s what a static site generator is: a program that generates static sites. You rerun the generator whenever you want make changes, then deploy the files it generates to a static web server.
(Some examples of static site generators: Jekyll, Hugo, Astro, Eleventy, Nanoc.)
Throughout the 2000s and 2010s, dynamic web site frameworks increasingly dominated — and become increasingly unwieldy. SSGs experienced a resurgence in popularity in the early 2020s or so. What’s the advantage? Static sites generally load faster, are easier to develop, are easier to deploy, are less costly to host, have fewer bugs in production, are less vulnerable to hacking, can be served on a CDN, and are less likely to create hosting company lock-in. Compared to dynamic frameworks that work in the browser (such as React), SSGs also tend to create sites that are more accessible and are compatible with more browsers and devices (though both of those are still difficult problems for any site).
Superfluous leans hard on Ruby’s dynamic capabilities such as metaprogramming and dynamic code evaluation to provide features such as ad hoc structured data access, concise templating, and hot reloading. It’s a good example of what Ruby is actually good for. Superfluous is undocumented (sorry!), so searching the web won’t help. Don’t worry; I’ll talk you through the relevant bits in these instructions.
This part of the assignment asks you spent a lot of time reading, but then make just a couple of small changes. Don’t be intimidated: this part has the longest instructions, but requires the least code.
Part 3 is new for 2026, and untested. I intend for it to be rich and interesting, but not laborious or difficult. That is my intent, but because students have never looked at it, I don’t really know how difficult it is. If you find that part 3 is taking a lot of your time, please let me know and I will adjust the assignment!
Get the SSG running
With a terminal open in the root of your local clone of ruby-adventures:
bundle exec superfluous plang-atlas --live
Keep that command running through the duration of this part of the assignment!
That command runs Superfluous in live development mode: it generates the web site you tell it to generate (plang-atlas in this case), then runs a mini-sized local web server on your computer that only allows connections from your computer. Open http://localhost:3000 in a web browser while Superfluous is running in dev mode. You should see a tiny, bare-bones web site titled “Atlas of Programming Languages.” Click through that site a little, and get a sense of its content.
While running, Superfluous’s live development mode watches all of the input files for changes. If it notices a change, it tells your browser to reload the page. In your browser, go the the Ruby page in the P-Lang Atlas, and leave that page open. Then in your text editor, open plang-atlas/src/data/languages/ruby.json. Change Ruby’s name to something else (maybe add an exclamation point, for example). Save the change, and your browser should reload the page and show the new name. If all of that worked, you’re up and running! Change the name back (or not, whatever), and on we go.
Study the code that’s there
Superfluous does two things:
- It reads all the files in a project’s
src/data, parses them if it knows how to parse them, then merges all of their content into a giant data tree made of Ruby objects. - It iterates over all the files in a project’s
src/presentation, each of which corresponds to one or more output files. Those presentation files can include templates that use data from the data tree.
The data
Superfluous turns everything in plang-atlas/src/data into Ruby objects, organized in one giant tree. For example, the property you changed that determines Ruby’s name becomes data.languages.ruby.name. Note that Superfluous has turned subdirectories, filenames, and properties from a JSON file into a single uniform data structure: there is a Ruby object data which has a property called languages, whose value is an object with a property called ruby, whose value is an object with a property called name, whose value is a string.
Those are all objects — yet nowhere in the code are there any class declarations that describe any of those properties. Superfluous made those properties just appear at runtime via the magic of Ruby’s metaprogramming.
If this were Java, the exact equivalent would be data.getLanguages().getRuby().getName() — and every one of those properties would have to appear in a class with a declaration. A Java implementation of this same “data tree” idea would probably bail on that approach, and would instead use a HashMap or similar: data.get("languages").get("ruby").get("name").asString(). At that point, the benefits of static type checking have gone out the window; we just have verbose, clumsy code to do a simple job.
Take a look at the files in plang-atlas/src/data. (There aren’t that many.) Think about the data tree they form.
The presentation
In your text editor, open plang-atlas/src/presentation/index.html.superf. This is the template for the web site’s home page.
Oh no! I don’t know HTML!
Don’t worry, you can fake your way through it for this assignment. Here’s a quick crash course by example in the few things you’ll need to know:
<h1>Amazing web page</h1> |
Top-level heading: a title for the whole page |
<h2>Exciting details</h2> |
Level 2 heading: the title of a subsection |
<ul> … </ul>
|
Unordered list (i.e. bullet points) |
<li> … </li>
|
One item in a list |
<a href="/some/page">Fun link</a> |
Clickable link to /some/page
|
If you want to learn more HTML, I recommend HTML for People.
That template uses a syntax called ERB, which is a way to mix little snippets of Ruby code into HTML (or other text). There are just two pieces of ERB syntax you need to understand:
<% thing %> |
Interprets thing as Ruby code |
<%= thing %> |
Evaluates thing as a Ruby rexpression, and inserts the result into the output |
Note that you can nest ERB inside a loop:
<% 10.times do %>
<p>Hi!</p>
<% end %>
This will output 10 paragraphs that say “Hi!”.
With this knowledge, study index.html.superf:
- Notice how it uses the data tree to loop over all tags and all languages, generating a list item for each.
- Notice how it uses Ruby’s
sort_byto sort both of those lists by name.
Now open plang-atlas/src/presentation/lang/{lang.id}.html.superf. Things to note:
-
There is a
script.rbsection containing Ruby code. Superfluous calls thebuildmethod. That method loops over the languages and callsrenderfor each, passing alangobject to the template below. Each call torenderbecomes a separate output file, with{lang.id}in the filename replaced with dynamic data, so that this one input file generates three output files:lang/python.html lang/ruby.html lang/java.html -
The template below uses
<% if lang.tags? %>to check whether thetagsproperty is present. If a language has no tags, it skips the whole “Tags” section. -
If there are tags for the language, the template loops over them. Where is (for example) the list of Ruby’s tags in the data files? Where are the tag names in the data files? How does the code map from tag IDs (like
oopanddyn_type) to tag names (like “Object-oriented” and “Dynamically typed”)?
You don’t need to understand everything in these files perfectly. It is OK to proceed with partial understanding! When you feel like you understand enough to make some changes…
Add some new data
Add an entry for the language you are doing for your Language Excursion in this class.
Does it deserve a new tag that’s not already present? If so, add the new tag.
You should be able to make your new language and new tag appear on the web site without modifying any files except in plang-atlas/src/data/.
Add a new property to the data
Add one new property to the languages or tags, and then display that property on the site. Some ideas:
- Add a year to the languages
- Add a one-sentence description to the languages or the tags
- Give tags the ability to have multiple related links, like languages
- Add a “see also” or “influenced by” field to the languages
- …or something you come up with! Amuse yourself!
This will require changing files in both plang-atlas/src/data/ and plang-atlas/src/presentation/. The changes should be fairly small, unless you decide to get fancy! (You’re welcome to go to town with building out the P-Lang Atlas site if you’re having fun, but for the purposes of this homework, a few small changes are all that’s necessary.)
As you make your changes, savor two things:
- Take note of the moment when the new thing you added in
datamagically shows up as an object property inpresentation. - Take note of how Superfluous’s hot reload can handle not only changes to data, but changes to Ruby code in your presentation files.
Both of those things would be much more difficult to do in a less dynamic language.
Hooray!
That’s it! When you’re done with all this, consider the optional bonus and then do your reflection.