Skip to content
Ken Davidson

Playing Around with Eleventy

Eleventy, TypeScript, Tailwind, Stimulus5 min read

Eleventy - yet another static site generator

So I've been toying with the idea of creating a web app for an NFL pool that I'm in. The guy that runs the pool is fantastic, but he's shot down all my attempts automating the process for him; in all honesty I respect him holding on. Since he isn't interested in a full application, all that I needed was a way to manage/view the Excel files he sends on a weekly basis - this opened the door to combining a static site generator with github pages (thanks to githubs schedule action trigger).

First off let's look at why I chose Elventy (11ty) for this:

  • Jimbo (the owner) had no interest in automating the process, which mean I only had to deal with Excel files he sent bi-weekly
  • The only automation was converting his Excel documents into HTML pages (with a sprinkle of Javascript)
  • I had zero interest in adding in the requirement of dealing with GraphQL; mainly due to my experience being limited and not wanting to spend the majority of my time on managing data.

Obviously there was a bunch of reading involved. I've said this before, and I'll most likely say it agian; when you're not a genius (I don't deny what i am) you need to make this up by being able to reverse design (engineer is such a bad term) and read a substantial amount of documentation and code. So let's jumpt in to what I needed:

Requirements

When I was reviewing options, I needed to:

Github Pages Support

Taking advantage of Github Pages (being cheap and all) was a huge requirement. Github pages is free (even though I'm a subscriber) and provide an inexpensive way for pages to be made available to the public. Since kenjdavidson.com is already published to Github Pages, it made sense to continue

Custom Data

I needed the ability to parse custom Excel files into data to be available on the site. When looking at the different options:

  • Gatsby requires GraphQL for custom data. Since all my GraphQL experience is solely theoretical, this didn't seem like a realistic choice to managing custom data.
  • Other SSG frameworks allow access to api content, but not from static files.

Eleventy's ability to convert static files (xlsx) into manageable JSON content seemed like the best choice. Eleventy manages this data in a cascade, which is really just a fancy way of saying priority. If you're used to the way that Spring Boot processes application.properties this shouldn't be a shock.

Jimbos NFL Pool

Jimbos NLF Pool

Jimbo runs a great pool, but he's old school and maintains pool picks/data in Excel instead of using one of the many third party sites. When Sunday rolls around we're always attempting to do the calculations to determine who is in the lead, what needs to happen for which results. I tried to get Jimbo into the present (without luck) so the next best thing was to just work with what he's providing (Excel) and provide the results. The important parts are:

  • showing a page containing the current standings
  • showing a page containing the current week picks (picks, points, etc).
  • showing a page for each player showing there season picks (by week) - this is more of a nice to have, but it was pretty straight forward.

A pretty simple idea, let's see how it happened:

If you're interesting is the finally project, you can see the source at https://github.com/kenjdavidson/jimbos-nfl-pool or the final result at https://kenjdavidson.com/jimbos-nfl-pool.

Eleventy (11ty)

First off, I've got to comment about how much I hate '11ty'. Like why? I get that Eleven = 11 + ty but this seems like even more ridiculous of. short form as i18n or k8s (which both drive me crazy). But, this is an old man story for another time.

Secondly, the site is super annoying. The tutorials are just links to articles and videos that I really don't want deal with. The documents cli page had the most information to get things started, it was pretty straight forward:

  • Create an NPM project
  • npm i -d @11ty/eleventy

which works ok for the basic site. While setting this up, I went with the following folder stucture:

1jimbos-nfl-pools
2|- content # Eleventy files
3|- src # Javascript/Typescript files
4\- styles # CSS files

Content

I chose to go with (most of the tutorials used) Nunjucks as the template engine. Not much really to say here, just that you'll need to have the Nunjucks docs site ready to go. The eleventy folder structure is pretty simple:

1jimbos-nfl-pools
2|- content # Eleventy files
3 |- _data # Stores the Excel files
4 |- _includes
5 \- layout.njk
6 |- index.njk
7 |- spread_pools.njk
8 \- players.njk
Data

As mentioned, the data files are Excel documents which contain:

  • Week details
  • Player picks
  • Weekly games with spreads
  • Pool standings (accumulated)

Since Eleventy provides some solid data processing, the only thing that was needed was adding the custom processor. This is configured through the eleventyConfig api:

1eleventyConfig.addDataExtension("xlsx", {
2 parser: parseSpreadPoolFile(eleventyConfig),
3 read: false
4 });

Which configures the following:

  • Whenever you see an xlsx file use the parseSpreadPoolFile (function provided by)
  • This file should not be read, but instead passed to the processor by filename

The processing code is available in the project repository. But essentially it just takes the sections processed above, and creates the data object.

Collections

Once we've got the data processed it will be available to to our pages using the data api. This data is not available through the collections (and therefore not available for pagination - this means a separate page would be required for each item (not cool). This means that all the data objects need to be massaged into a couple of collections:

The two collections needed to match my requirements are: spread_pools and players, again using the eleventyConfig api:

1// Collection
2 eleventyConfig.addCollection("spread_pool", spreadPoolsCollection);
3 eleventyConfig.addCollection("players", playersCollection);

In this case we need to reduce all the spread pool file data into a single collection:

1module.exports = function (collectionsApi) {
2 const data = collectionsApi.getAll()[0].data;
3 return Object.entries(data)
4 .filter(([k, v]) => "spread_pool" === v.type)
5 .map(([k, v]) => ({
6 ...v,
7 name: k,
8 }));
9};

The players collection is a little more intense, as it performs some processing of all the spread_pool (weekly data) and merges/reduces the game results and standings with the weekly player picks.

Styles

I know this is the third (sorting) folder in the list; but as a live site it was important that there was some style. I'm not a designer, which means I'm ok with pages looking a little less amazing if they flow and work well. I also love libraries/frameworks that have a pretty decent community and a good number of pre-designed components. I fully acknowledge that I'm a developer, not a designer. For no other reason than that I've been using it more and more often, I decided to go with tailwindcss.

Setting up Tailwind was pretty straight forward. Since this project is a static site, it made sense to use the tailwind cli.

1jimbos-nfl-pools
2|- css
3 \- styles.css
4\- tailwind.config.js

At this point there is only a single styles.css file which does all the tailwind goodness:

1@tailwind base;
2@tailwind components;
3@tailwind utilities;
4
5// add some custom layer base/components

JavaScript

I've been using @hotwired/stimulus in my more recent projects, so I figured I'd keep the ball rolling in this one. Stimulus has Typescript support through webpack which is how I decided get things setup. This gives me the option of keeping the javascript and css separate or eventually join them together at some point.

The project structure for javascript is:

1jimbos-nfl-pools
2|- src
3 |- controllers
4 \- stimulus.ts
5|- tsconfig.json
6|- webpack.config.js

Package.json

To get everything running nicely, package.json provides the following scripts:

1"scripts": {
2 ...
3 "dev": "run-p dev:*",
4 "dev:11ty": "eleventy --serve --watch",
5 "dev:css": "tailwindcss -i ./css/styles.css -o ./_site/styles.css --watch",
6 "dev:webpack": "webpack --watch",
7 "build": "NODE_ENV=production npm-run-all clean build:*",
8 "build:11ty": "eleventy --pathprefix=jimbos-nfl-pool",
9 "build:css": "tailwindcss -i ./css/styles.css -o ./_site/styles.css --minify",
10 "build:webpack": "webpack",
11 ...
12}

npm run dev gets things going!!

Deploying to Github Pages

Like all other static site generators, Eleventy works well with Github Pages; which is how I decided to publish the site. There are two main issues related to deployment that still need to be addressed:

Data File Updates

This one I haven't dealt with yet, but in order to deploy a new week the Excel file needs to be uploaded to the project. The files are emailed out at this point (and as I mentioned Jimbo has no interest in changing this):

  • Manually upload the file
  • Pull the email/file from my email at the beginning of the week

Future Ken's problems

Deploying Updates

At this point the site is completely static - which means all the game results are updated only when the site is generated (and deployed). Github actions provide the ability to fire deployments on schedule using cron expressions. The most annoying part of this was converting EST to UTC. The following section of the configuration provides generation on:

  • manual request
  • push to main
  • on schedule attempting to match the end/beginning of each game group
1name: Build and Release
2run-name: ${{ github.actor }} is releasing new spread pool results
3on:
4 workflow_dispatch:
5 push:
6 branches:
7 - main
8 schedule:
9 # Thursday 11:45pm = Friday 4:45am
10 # Sunday 11:45am, 3:45pm = Sunday 4:45pm, 8:45pm
11 # Sunday 7:45pm, 11:45pm = Monday 12:45, 4:45am
12 # Monday 11:45pm = Tuesday 4:45am
13 - cron: '45 0,4 * * 1,2,5'
14 - cron: '45 16,20 * * 0'

I'll slowly start adding in Stimulus controllers to handle the updates between deployments (things like in game updates and live leaderboard).

© 2023 by Ken Davidson. All rights reserved.