Zach’s ugly mug (his face) Zach Leatherman

Lessons learned moving Eleventy from CommonJS to ESM

February 07, 2024

This talk was given at 2024.

Watch on YouTube: Lessons learned moving Eleventy from CommonJS to ESM

And the full slide deck is included below:

11ty v3CommonJSCommon, the actor and rapper is shown alongside the JS logoCommonJS is strikethrough, ESM is addedRegina George from Mean Girls is shown riding in a lunar module and says Get in Loser—We’re using Modules nowmodule.exports = function() {};export default function() {};const render = function() {}; module.exports = { render };const render = function() {}; export { render };A screenshot of the Eleventy core test suite passing on GitHub actions (1048 tests) on Mac/Linux/WindowsRyan Reynolds in medical scrubs asks Why?EMCAScript modules are the official standard format to package JavaScript code for reuse. (via TC39 and the node.js documentation)A screenshot of the web browser support for EMCAScript 2015 (all green)A table showing CommonJS support in Node but missing in Deno and the Browser but ESM supported in all.Bundlers! Rollup, esbuild, Carabiner, Parcel, Webpack, SWCActually sorry Carabiner is just the Toblerone logoA repeat of the CommonJS/ESM support in Node, Deno, BrowserMinimum viable tech stack means we stay as close to the Build/no-build line as possible, avoiding as many dependencies as we can to stick as close to HTML/CSS/JS as we can.A fake messages conversation about web sites being easier to maintain because they do less!Eleventy v1 was 72.7MB of node_modules, v2 was 35.2MBEleventy v3 is currently 20.2MB of node_modulesFor comparison, rollup is 4.8MB, webpack is 25.9MB, parcel is 281.9MB, esbuild is 9.9MB and swc is 47MB.Gatsby is 523.8MBEleventy v3 is dropping some node_modules weight through our community survey results. Handlebars, Pug, Mustache, EJS, and Haml are all unpopular template syntaxes and we’ll move these into plugin-land.RequirementsNode.js 10 ESM was experimental but it’s stable in 1211ty v1 required Node.js 12+, v2 required 14+, v3 requires 18+We could have done this in 2022, but we didn’t 😅CompatibilityCommonJS Project compatibility, config files, data files, template files, third party plugins.The biggest limitation with a CommonJS project is that you can’t require an ESM dependency (which Eleventy is, now)Compatibility overview.CommonJS Upgrade Walkthrough in Two StepsStep one—npm install @11ty/eleventy@canary --save-exactYou cannot require("@11ty/eleventy") now so using bundled Eleventy plugins need to change.Change your config callback to async and use await import("@11ty/eleventy") to include them instead.Done.Now let’s talk about Third-party pluginsExisting third-party plugins are CommonJS—keep these as-is.eleventyConfig.addPlugin(async function() {}) now supported in Eleventy v3Any new ESM Eleventy Plugins are only compatible with Eleventy v3ESM Eleventy plugins require an `async` config callback or an ESM configuration file—both of which are only supported in Eleventy v3Our compatibility checklist for CommonJS projects is complete.Next, ESM application code.A similar checklist of config files, data files, templates, and plugins.ESM Upgrade Walkthrough in Three StepsStep one npm install @11ty/eleventy@canary --save-exact, Step two add type: 'module' to your package.json. All .js files in your project are now assumed to be ESM.Step three rename all .js app files to .cjs (and convert these over to ESM as-needed or not at all)Done.Let’s convert our configuration code to ESMUse import {} from "@11ty/eleventy" as normal. Change module.exports = to export.defaultChange data files from module.exports = to export.defaultYou can used named exports too. export { key }export default { data, render } in an index.11ty.js template.export { data, render } in an index.11ty.js template.__filename and __dirname aren’t available by default but you can use import.meta.url and fileURLToPath to recreate these.Eventually we’ll use import.meta.filename and import.meta.dirname (not yet stable as of Node 21)Let’s talk about Third-party plugins in an ESM project.Everything will work as-is, you can import any ESM or CJS plugin from an ESM config file. Access to all the things!The full checklist of compatibility is complete.Node Scripts, Eleventy’s Programmatic APIAgain, you can’t require("@11ty/eleventy");Dynamic await import("@11ty/eleventy") again.If your script is ESM, you can `import Eleventy from "@11ty/eleventy"` directly.InternalsUpgrade frozen CommonJS dependencies (these went full ESM), @sindresorhus/slugify, multimatch, bcp-47-normalizeReimporting without require.cache to get new contentBefore we would delete from require.cache and re-require the file. Now we await import with a cache buster URL parameter.Parsing the dependency tree. Before this was available via require.cache. Now we parse the JS with acorn (but it only happens for --serve/--watch)Import attributes, before we could require a JSON file directly. You can’t await import a JSON file now.You could import a JSON file: assert { type: 'json' } with a warningThe syntax changed to: with { type: 'json' } also with a warningWe just use fs.readFile* to avoid warnings.Dynamic execution of JavaScript for `---node` front matter.Node’s vm module is only stable for CommonJS code. Fine until you try to `import` something.You can use node --experimental-vm-modules but we fake it by transforming `import` to dynamic `import()` until this is stable.ConclusionsESM ~~all~~ of the thingsESM some of the things.It’s better to be an ESM dependent.It’s better to be a CommonJS dependency.ESM for me, CommonJS for thee (fancy Winnie the Pooh meme)Titus Wormer’s chart on the popularity of ESM/CJS across popular npm packages. November 2023 had 68.8% CJS, 12.9% Faux, 7.5% Dual, 10.8% ESM11ty and Astro are ESM first—Svelte, Vite, Nuxt are dual—Remix and Gatsby are faux—Next.js is CJS.For me, I will not be dual publishing packages. I don’t want the overhead.Looking at package compatibility—if things are going to include you, it’s tempting to use CommonJS . If you’re going to use other things, it’s better to be ESM.But we have to think of the wider ecosystem outside of Node.js (other runtimes and the Browser).The Parasite movie poster.The Parasite movie poster overlaid with Parasites vs. Para-apps.Thanks! (the end)

Correction: on Slide 40 the code reads eleventyPlugin.addPlugin when it should say eleventyConfig.addPlugin.

< Newer
throbber Web Component
Older >
carouscroll Web Component

Zach Leatherman IndieWeb Avatar for a builder for the web and the creator/maintainer of IndieWeb Avatar for https://www.11ty.devEleventy (11ty), an award-winning open source site generator. At one point he became entirely too fixated on web fonts. He has given 81 talks in nine different countries at events like Beyond Tellerrand, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Formerly part of CloudCannon, Netlify, Filament Group, NEJS CONF, and NebraskaJS. Learn more about Zach »


IndieWeb Avatar for https://mxb.devZach Leatherman :verify:Joseph Kohlmannd3v1an7CFE.devBob MonsourCory :prami_pride_demi:Eleventy ???? v3.0.0-alpha.4lime360Lea RosemaBjörn BerkholzJean Pierre KolbHasan Ali


Joseph KohlmannCosti(n)Simon Cox :SEO:derek :prami:d3v1an7ApocraphiliaDavid McCormicknrk 9819Jared WhiteRodneyEric PortisEleventy ???? v3.0.0-alpha.4Lea Rosemalime360Óscar GarcíaHasan AliDaniel ????️‍???? Jilg
  1. nrk 9819

    nrk 9819

    @zachleat @eleventy @cfedev great job. @eleventy should be more popular, it is simple and straightforward.

  2. Jared White

    Jared White

    @zachleat @eleventy @cfedev I really appreciate all the details here. I'm getting ready to go default ESM for the esbuild / postcss / sass config in Bridgetown going forward…a dramatically simpler process since most code is in Ruby, but still it's helpful to see the road … Truncated

  3. Zach Leatherman :verify:

    Zach Leatherman :verify:

    @nrk9819 @eleventy @cfedev we’ll just keep going ????

  4. Zach Leatherman :verify:

    Zach Leatherman :verify:

    @jaredwhite @eleventy @cfedev great! I hope it works out well for y’all!

  5. Lou Cyx

    Lou Cyx

    @zachleat great talk! I took some notes to update my ESM packages (I didn't knew about `assert` changing to `with` and `import.meta.filename`/`dirname`). Thank you for sharing, Zach!

Shamelessly plug your related post

These are webmentions via the IndieWeb and

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)