CCF Part 1: Font Management

2025-11-15

I made this because I really couldn't find a tool that does this. Here are my goals:

  • Use google fonts
  • Have all of my project locally (No CDN links)
  • Doesn't use NPM

Looking around, the most obvious solution to this problem is Fontsource, but unfortunately, that would require depending on the layout of node_modules and using a tool, probably a bundler, to manage that. This is a huge amount of complexity for something that is relatively simple:

  • download some font files
  • generate appropriate snippets of CSS

To that end, I found another tool Hermes which does everything I want as a CLI. After trying it out, I thought it would be nice to have a config file format instead of a CLI for this, so I made this PR: install command by quinn · Pull Request #7 · cadensstudio/hermes · GitHub. I've been using this branch of the project since and it's been working well, but since it still hasn't been merged I thought it would be nice to just setup a separate CLI for using the config based tool.

To get started, install using Go:

go install go.quinn.io/ccf@latest

How it Works

First, create a yaml config file:

---
dir: "./public/fonts"
stylesheet: "./css/fonts.css"
import: "../fonts/"
fonts:
  - family: "Anonymous Pro"
    variants:
      - "700"
      - "700italic"
      - "regular"
      - "italic"

You can use a flag to change the font file which defaults to ./fonts.yaml, but please consider using the 4 letter .yaml file extension unless you are using an MS-DOS based operating system.

Next, create a Google API key. This should be free: Google Cloud Platform. Export it as GFONTS_KEY in your env, or pass it as a command flag.

Then, run ccf fonts. Here's what running the command creates:

$ tree public/
public/
├── css
│   └── fonts.css
└── fonts
    ├── Anonymous Pro_700italic.woff2
    ├── Anonymous Pro_700.woff2
    ├── Anonymous Pro_italic.woff2
    └── Anonymous Pro_regular.woff2

Contents of fonts.css:

@font-face {
  font-family: 'Anonymous Pro';
  font-style: normal;
  font-weight: 700;
  src: url('../fonts/Anonymous Pro_700.woff2') format('woff2');
}

@font-face {
  font-family: 'Anonymous Pro';
  font-style: italic;
  font-weight: 700;
  src: url('../fonts/Anonymous Pro_700italic.woff2') format('woff2');
}

@font-face {
  font-family: 'Anonymous Pro';
  font-style: normal;
  font-weight: 400;
  src: url('../fonts/Anonymous Pro_regular.woff2') format('woff2');
}

@font-face {
  font-family: 'Anonymous Pro';
  font-style: italic;
  font-weight: 400;
  src: url('../fonts/Anonymous Pro_italic.woff2') format('woff2');
}

You can then import or link the generated stylesheet to load the fonts.

Final Thoughts

Bundler Rot

It's interesting how so many tools funnel you into using a bundler. Even though it's no longer necessary (ESM+HTTP/2) the tooling is so built up around the assumption that a bundler is present, it ends up being more convenient to use one, which proliferates the problem.

ESM Vendoring Tool

It looks like something that correctly caches ESM modules could work, for example: open-sans@5.2.7 this is a "module" that provides a stylesheet and font files. It would be possible with a vendoring tool to get the stylesheets and fonts that way, without using npm or a CDN.

From my research, this also does not exist. Maybe deno vendor (Now removed in v2) sort-of does it, but a purpose-built tool would be great. I'm gonna start building this soon.

Open Source Etiquette

Personally, if someone opened a PR on my little side project tool with 10 stars, I'd be fucking stoked. Given that, I didn't really prod too much to get it merged, it didn't seem like the author was interested. Their code is MIT licensed, so it seemed simple enough and the less obtrusive to just set my thing up rather than hounding them to get my code merged.