Porting to 11ty v3 and TypeScript

Recently, I’ve been porting my various personal websites over to 11ty v3 — which is still officially in a late-alpha release stage, but has been very stable for me. It also includes a host of improvements and new stuff. At the same time I’ve been upgrading, I’ve also been converting my 11ty config file from JavaScript to TypeScript.

My recipes site is (at least currently) the only part of the mighty genehack web empire where the source is publicly available, so I thought I’d blog about the process of doing that one, just for fun (and to document the process on the off chance I ever need to do it again)

So, we’re gonna start at this innocuous commit from a couple months ago. As a starting point, we’re gonna install 11ty v3, and then also upgrade all the other packages:

> npm i @11ty/eleventy@v3.0.0-alpha.17

added 30 packages, removed 8 packages, changed 12 packages, and audited 341 packages in 4s

91 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

That was easy enough! Let’s commit that:

> git add package*json

> git ci -m"upgrade to 11ty v3"
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
[port-to-eleventy-3-and-typescript 582752a] upgrade to 11ty v3
 2 files changed, 897 insertions(+), 744 deletions(-)

And then let’s go ahead and upgrade everything else, and make sure we don’t have any security-related issues to clean up:

> npm up
npm warn deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported

added 5 packages, removed 6 packages, changed 11 packages, and audited 340 packages in 5s

91 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

> npm audit
found 0 vulnerabilities

> git add package-lock.json

> git ci -m"npm up"
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
[port-to-eleventy-3-and-typescript 789f356] npm up
 1 file changed, 377 insertions(+), 169 deletions(-)

Sweet — next we’re gonna convert everything over to ESM. This is mostly adding "type": "module" to the package.json (although there were a couple other tweaks to the eslint config section too) and then converting the .eleventy.js config syntax over — mostly changing the require statements to import, and modifying how the config does its export. You can see all the details in the relevant commit. Let’s make sure things still build:

> npm run build

> recipes.genehack.org@1.0.0 build
> eleventy

(node:97927) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
[11ty] Writing ./_site/css/site.css from ./site.scss
[11ty] Writing ./_site/css/normalize.css from ./css/normalize.css
[11ty] Writing ./_site/index.html from ./index.html (liquid)
[11ty] Writing ./_site/404.html from ./404.md (liquid)
[11ty] Writing ./_site/recipes/balsamic-vinaigrette/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/braised-pork-shoulder-steak/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/buffalo-sauce/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/garlic-herb-rump-roast/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/peposo/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/roasted-tomato-soup/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/salted-spatchcocked-chicken/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/sauteed-spinach/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/sirloin-tip-roast/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/smashed-potatoes/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/wangs/index.html from ./recipes.md (liquid)
[11ty] Copied 16 Wrote 15 files in 0.36 seconds (v3.0.0-alpha.17)

Excellent — now let’s actually switch over to using TypeScript for the config. This guide on the JetBrains blog was super helpful in figuring out how to get going with this. The high-level overview is:

  1. Rename .eleventy.js to eleventy.config.ts
  2. Add a tsconfig.json
  3. Install tsx so we can directly invoke TypeScript code
  4. Update our package.json scripts to invoke 11ty via tsx
  5. Validate things still build

Again, all the nitty-gritty details are in this commit, but the first few steps looked like:

> git mv .eleventy.js eleventy.config.ts

> npm i --save-dev tsx

added 5 packages, and audited 345 packages in 3s

93 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

# change all `eleventy` invocations in `package.json` to
# `tsx node_modules/@11ty/eleventy/cmd.cjs --config=eleventy.config.ts`

> npm run build

> recipes.genehack.org@1.0.0 build
> tsx node_modules/@11ty/eleventy/cmd.cjs --config=eleventy.config.ts

(node:6420) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
[11ty] Writing ./_site/css/site.css from ./site.scss
[11ty] Writing ./_site/css/normalize.css from ./css/normalize.css
[11ty] Writing ./_site/index.html from ./index.html (liquid)
[11ty] Writing ./_site/404.html from ./404.md (liquid)
[11ty] Writing ./_site/recipes/balsamic-vinaigrette/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/braised-pork-shoulder-steak/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/buffalo-sauce/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/garlic-herb-rump-roast/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/peposo/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/roasted-tomato-soup/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/salted-spatchcocked-chicken/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/sauteed-spinach/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/sirloin-tip-roast/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/smashed-potatoes/index.html from ./recipes.md (liquid)
[11ty] Writing ./_site/recipes/wangs/index.html from ./recipes.md (liquid)
[11ty] Copied 16 Wrote 15 files in 0.29 seconds (v3.0.0-alpha.17)

Woot! At this point, technically our conversion to TypeScript is complete — but the config code hasn’t actually been type-annotated yet, it’s working because all valid JavaScript is valid TypeScript. To actually get any benefit out of this conversion, I also needed to go back and type-annotate the config code. As usual, the gory details can be found in the commit; the basic process was:

  1. Install missing type annotation support
  2. Step through the config and add type annotations

One somewhat non-obvious thing was figuring out the type of the config variable that is passed to the default configuration function. Turns out this is typed as UserConfig, and you can access it by adding this to your config file:

import { UserConfig } from "@11ty/eleventy";

And that’s it! recipes.genehack.org is being built with 11ty v3 and a config written in TypeScript!