Notes on Upgrading to Eleventy 3.0
My blog is now generated by Eleventy 3.0, which was released last month. The upgrade process took me a few hours split over two evenings and I took a few notes. This probably isn’t an interesting write up for hardly anyone – but if you have any of the same 11ty concerns I do, you may find a solution in here.
-
I started out by following the instructions in the excellent Upgrade Helper docs.
-
When I tried to install the Upgrade Helper plugin, I got a dependency conflict with
eleventy-sass:npm ERR! code ERESOLVE npm ERR! ERESOLVE could not resolve npm ERR! npm ERR! While resolving: eleventy-sass@2.2.4 npm ERR! Found: @11ty/eleventy@3.0.0 npm ERR! node_modules/@11ty/eleventy npm ERR! dev @11ty/eleventy@"3.0" from the root project npm ERR! npm ERR! Could not resolve dependency: npm ERR! peer @11ty/eleventy@"^1.0.0 || ^2.0.0-canary.12 || ^2.0.0-beta.1" from eleventy-sass@2.2.4 npm ERR! node_modules/eleventy-sass npm ERR! dev eleventy-sass@"^2.2.3" from the root project npm ERR! npm ERR! Conflicting peer dependency: @11ty/eleventy@2.0.1 npm ERR! node_modules/@11ty/eleventy npm ERR! peer @11ty/eleventy@"^1.0.0 || ^2.0.0-canary.12 || ^2.0.0-beta.1" from eleventy-sass@2.2.4 npm ERR! node_modules/eleventy-sass npm ERR! dev eleventy-sass@"^2.2.3" from the root projectUpgrading
eleventy-sasswithnpm upgrade eleventy-sassresolved it and I was able install the Upgrade Helper successfully.
-
When I ran my site I got this error:
(node:2948) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. (Use `node --trace-warnings ...` to show where the warning was created) [11ty] Eleventy Error (CLI): [11ty] 1. Error in your Eleventy config file 'eleventy.config.js'. (via EleventyConfigError) [11ty] 2. Cannot use import statement outside a module (via SyntaxError) [11ty] [11ty] Original error stack trace: /Users/harris/Projects/Personal/chromamine.com/eleventy.config.js:1 [11ty] import UpgradeHelper from "@11ty/eleventy-upgrade-help" [11ty] ^^^^^^ [11ty] [11ty] SyntaxError: Cannot use import statement outside a moduleWhoops – I had copied the line from the docs as written
import UpgradeHelper from "@11ty/eleventy-upgrade-help"but I had forgotten to convert my project to use ES Modules (ESM) imports instead of CommonJS (CJS) imports (e.g.,
const UpgradeHelper = require("@11ty/eleventy-upgrade-help")).I could have stuck to CommonJS imports, which 11ty still supports, but most of the reason I wanted to upgrade to 3.0 was because it supports ESM imports – which are more consistent with how I write Javascript everywhere else and seem to be the future of the language. I added
"type": "module"to mypackage.jsonand converted all of my old CJS imports and exports to ESM.This wasn’t a straightforward replace because I sometimes used CJS
requiremid-function, but I moved all imports to the top when I converted them. I rearranged some of the exports as well. I was able to ask Copilot to convert them for me (and add extensions, because node requires extensions for local ESM imports) successfully. It still took a while to go through each file and convert them block by block. I did occasionally have to remind it not to insert semicolons, since my javascript style is, controversially, not to use semicolons.It’s possible I could have asked Copilot to do it project-wide and it would have worked – but I wasn’t prepared to trust it that far without checking its work. Nevertheless I do think it saved me a fair bit of time here.
This also enabled me to remove some hacky workarounds I had in my config for importing ESM-only projects, e.g., this code at the top of my config function:
eleventyConfig.on('eleventy.before', async () => { const d3time = await import("d3-time-format") const d3format = await import("d3-format") global.d3time = d3time global.d3format = d3format const { compileObservable } = await import("./config/utils/ojs/compile.mjs") global.compileObservable = compileObservable })became a more civilized
import { format as d3Format, utcFormat as d3UtcFormat, } from 'd3'in the specific file that needed it (note also that the imported objects are also more specific).
-
eleventy-sass, it turns out, requires a node flag--experimental-require-moduleto run. I have these"script"s in mypackage.jsonfile to run 11ty:"scripts": { "serve": "PROD=0 eleventy --serve", "build": "PROD=1 eleventy", //... }It wasn’t immediately obvious to me how to add the node flag to these scripts, but after searching around, I settled on this approach:
"scripts": { "serve": "PROD=0 NODE_OPTIONS=\"--experimental-require-module\" eleventy --serve", "build": "PROD=1 NODE_OPTIONS=\"--experimental-require-module\" eleventy", //... } -
After fixing all of the above, I was finally able to run
npm run buildand see the output from the upgrade helper plugin. Mostly my project passed the tests:[11ty/eleventy-upgrade-help] PASSED You are using Node v22.0.0. Node 18 or newer is required. [11ty/eleventy-upgrade-help] PASSED Eleventy will fail with an error when you point `--config` to a configuration file that does not exist. You are not using `--config`—so don’t worry about it! Read more: https://github.com/11ty/eleventy/issues/3373 # ...But there was one error:
[11ty/eleventy-upgrade-help] ERROR Found 10 pug files in your project but the pug plugin was moved from core to an officially supported plugin. You will need to add the plugin for pug, available here: https://github.com/11ty/eleventy-plugin-template-languages and you can learn more about this here: https://github.com/11ty/eleventy/issues/3124I followed the instructions to install the Pug template plugin, which resolved the error.
-
Previously I used a bit of a hack to make my 11ty template filters available to my Pug templates:
global.filters = eleventyConfig.javascriptFunctions eleventyConfig.setPugOptions({ globals: ['filters'], })which allowed me to call filters in templates like:
span.archive-list__item-tag-count= `(${tag.count} post${filters.pluralize(tag.count)})`This no longer worked with in 3.0 and with the new plugin (I believe both
.javascriptFunctionsand.setPugOptionsare gone). This new code worked, with no changes to my templates:eleventyConfig.addPlugin(pugPlugin, { filters: eleventyConfig.getFilters() })though I do wonder if I’m misusing the
filtersoption. -
Next, I got this error when building:
[11ty] Problem writing Eleventy templates: [11ty] Output conflict: multiple input files are writing to `./_site/| tags/#{ filters.slugify(tag) }/index.html`. Use distinct `permalink` values to resolve this conflict. [11ty] 1. ./src/tags.pug [11ty] 2. ./src/tags.pug [11ty] 3. ./src/tags.pug [11ty] 4. ./src/tags.pug # ... and on and on for a whileI was using Eleventy’s pagination feature to generate all my tag pages from a single template:
tags.pug’s frontmatter:pagination: data: collections size: 1 alias: tag filter: - posts - all layout: monotheme/page.pug permalink: "| tags/#{ filters.slugify(tag) }/"The
permalinkentry in the YAML frontmatter is actually a mini Pug template that generates a permalink with each tag name, like/tags/11ty/. Unfortunately in 3.0 handling of permalinks changed and this code no longer worked. Fortunately it was very easy to fix – just a matter of nesting thepermalinkentry under aeleventyComputedentry:pagination: data: collections size: 1 alias: tag filter: - posts - all layout: monotheme/page.pug eleventyComputed: permalink: "| tags/#{ filters.slugify(tag) }/"(Thanks to Shiv for helping me out with this one in the 11ty Discord!)
-
And one last issue: I use
eleventy-sassandeleventy-plugin-revto add hashes to compiles CSS and javascript files. This required callingfilters.rev(cssURL)around URLs in my templates. Because of my changes to how I was passing filters to Pug, this stopped working. After digging around in the code foreleventy-plugin-revfor a bit, I settled on this code to solve it:eleventyConfig.addFilter("rev", pluginRev.revvedFilePathFromOutputPath)which worked.
Finally, my site compiled successfully under 11ty 3.0. I’m looking forward to being able to use ESM imports to clean up my code a bit, leverage the asynchronous features for improved build performance, adding new features to my blog with the APIs that the new version makes available (Virtual Templates, included Bundler, and page.rawInput seem especially intriguing), and possibly experiment with some of the new template languages (looking at you, WebC) that are now available to me.