— Eleventy, TypeScript, Tailwind, Stimulus — 5 min read
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:
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:
When I was reviewing options, I needed to:
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
I needed the ability to parse custom Excel files into data to be available on the site. When looking at the different options:
GraphQL
for custom data. Since all my GraphQL experience is solely theoretical, this didn't seem like a realistic choice to managing custom data.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.
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:
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.
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:
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-pools2|- content # Eleventy files3|- src # Javascript/Typescript files4\- styles # CSS files
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-pools2|- content # Eleventy files3 |- _data # Stores the Excel files4 |- _includes 5 \- layout.njk6 |- index.njk7 |- spread_pools.njk8 \- players.njk
As mentioned, the data files are Excel documents which contain:
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: false4 });
Which configures the following:
xlsx
file use the parseSpreadPoolFile
(function provided 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.
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// Collection2 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.
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-pools2|- css3 \- styles.css4\- 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
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-pools2|- src3 |- controllers4 \- stimulus.ts5|- tsconfig.json6|- webpack.config.js
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!!
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:
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):
Future Ken's problems
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:
1name: Build and Release2run-name: ${{ github.actor }} is releasing new spread pool results3on:4 workflow_dispatch: 5 push:6 branches: 7 - main8 schedule:9 # Thursday 11:45pm = Friday 4:45am10 # Sunday 11:45am, 3:45pm = Sunday 4:45pm, 8:45pm11 # Sunday 7:45pm, 11:45pm = Monday 12:45, 4:45am12 # Monday 11:45pm = Tuesday 4:45am13 - 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).