惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
Vercel News
Vercel News
D
DataBreaches.Net
MongoDB | Blog
MongoDB | Blog
H
Help Net Security
小众软件
小众软件
美团技术团队
T
The Blog of Author Tim Ferriss
爱范儿
爱范儿
D
Docker
Martin Fowler
Martin Fowler
大猫的无限游戏
大猫的无限游戏
博客园 - 聂微东
Blog — PlanetScale
Blog — PlanetScale
H
Hackread – Cybersecurity News, Data Breaches, AI and More
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
V2EX
S
SegmentFault 最新的问题
云风的 BLOG
云风的 BLOG
B
Blog
雷峰网
雷峰网
The Cloudflare Blog

Max Böck

Faster Horses A year in review: 2024 Going Buildless Live CMS Previews with Sanity and Eleventy Old Dogs, new CSS Tricks A year in review: 2023 7 Reasons why I don't write The IndieWeb for Everyone Make Free Stuff Media Queries in Times of @container Container Queries in Web Components Asset Pipelines in Eleventy Medium-Style Share Highlights in Eleventy Space Jam Webmention Analytics Making Persistent Build Folders in Netlify A year in review: 2020 Whimsical Website Club The Return of the 90s Web Human Code Color Theme Switcher Eleventy Résumé Builder The Emergency Website Kit Making a Gigposter A year in review: 2019 Webclerks Conference Roads IndieWeb Link Sharing Good Enough The CSS Mindset
Upgrading to Eleventy v3
Max Böck · 2024-06-01 · via Max Böck

I took some time this week to upgrade my site to the newest version of Eleventy. Although v3.0.0 is still in alpha, I wanted to give it a try.

This iteration of mxb.dev is already 7 years old, so some of its internal dependencies had become quite dusty. Thankfully with static sites that didn’t matter as much, since the output was still good. Still, it was time for some spring cleaning.

Switching to ESM

Permalink to “Switching to ESM”

A big change in v3 is that Eleventy is now using ECMAScript Module Syntax (ESM). That brings it in line with modern standards for JS packages.

In “Lessons learned moving Eleventy from CommonJS to ESM”, Zach explains the motivation for the switch.

I’ve already been using ESM for my runtime Javascript for quite some time, and I was very much looking forward to get rid of the CommonJS in my build code. Here’s how to switch:

Step 1: Package Type

Permalink to “Step 1: Package Type”

The first step is to declare your project as an environment that supports ES modules. You do that by setting the type property in your package.json to “module”:

//package.json
{
    "name": "mxb.dev",
    "version:": "4.2.0",
    "type": "module",
    ...
}

Doing that will instruct node to interpret any JS file within your project as using ES module syntax, something that can import code from elsewhere and export code to others.

Step 2: Import Statements

Permalink to “Step 2: Import Statements”

Since all your JS files are now modules, that might cause errors if they still contain CommonJS syntax like module.exports = thing or require('thing'). So you’ll have to change that syntax to ESM.

You don’t need to worry about which type of package you are importing when using ESM. Recent node versions support importing CommonJS modules using an import statement.

Starting with node v22, you can probably even skip this step entirely, since node will then support require() syntax to import ES modules as well.

In an Eleventy v2 project, you’ll typically have your eleventy.config.js, files for filters/shortcodes and global data files that may look something like this:

const plugin = require('plugin-package')
// ...
module.exports = {
    something: plugin({ do: 'something' })
}

Using ESM syntax, rewrite these files to look like this:

import plugin from 'plugin-package'
// ...
export default {
    something: plugin({ do: 'something' })
}

There are ways to do this using an automated script, however in my case I found it easier to go through each file and convert it manually, so I could check if everything looked correct. It only took a couple of minutes for my site.

It’s also helpful to try running npx eleventy --serve a bunch of times in the process, it will error and tell you which files may still need work. You’ll see an error similar to this:

Original error stack trace: ReferenceError: module is not defined in ES module scope
[11ty]  This file is being treated as an ES module because it has a '.js'
        file extension and 'package.json' contains "type": "module".
        To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
[11ty]  at file://mxb/src/data/build.js?_cache_bust=1717248868058:12:1

If you absolutely have to use CommonJS in some files, renaming them to yourfile.cjs does the trick.

Gotchas

Permalink to “Gotchas”

Some minor issues you may encounter:

  • the minimum required node version for v3 is node 18.
  • you may need to npm install with the --legacy-peer-deps flag if some of your deps have trouble with the alpha release.
  • if you used __dirname in your CJS files, you might have to replace that with import.meta.url
  • if you import something like a json file, you might need to specify the type:
    import obj from "./data.json" with { "type": "json" }

Eleventy Image Transform

Permalink to “Eleventy Image Transform”

Eleventy v3 also comes with a very useful new way to do image optimization. Using the eleventy-img plugin, you now don’t need a shortcode anymore to generate an optimized output. This is optional of course, but I was very eager to try it.

Previously, using something like an async image shortcode, it was not possible to include code like that in a Nunjucks macro (since these don’t support asynchronous function calls).

In v3, you can now configure Eleventy to apply image optimization as a transform, so after the templates are built into HTML files.

Basically, you set up a default configuration for how you want to transform any <img> element found in your output. Here’s my config:

// eleventy.config.js
eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
    extensions: 'html', // transform only <img> in html files
    formats: ['avif', 'auto'], // include avif version and original file type
    outputDir: './dist/assets/img/processed/', // where to write the image files
    urlPath: '/assets/img/processed/', // path prefix for the img src attribute
    widths: ['auto'], // which rendition sizes to generate, auto = original dimensions
    defaultAttributes: {
        // default attributes on the final img element
        loading: 'lazy',
        decoding: 'async'
    }
})

Now that will really try to transform all images, so it might be a good idea to look over your site and check if there are images that either don’t need optimization or are already optimized through some other method. You can exclude these images from the process by adding a custom <img eleventy:ignore> attribute to them.

All other images are transformed using the default config.
For example, if your generated HTML output contains an image like this:

<img
    src="bookcover.jpg"
    width="500"
    alt="Web Accessibility Cookbook by Manuel Matuzovic"
/>

The plugin will parse that and transform it into a picture element with the configured specs. In my case, the final HTML will look like this:

<picture>
    <source
        srcset="/assets/img/processed/Ryq16AjV3O-500.avif 500w"
        type="image/avif"
    />
    <img
        src="/assets/img/processed/Ryq16AjV3O-500.jpg"
        width="500"
        alt="Web Accessibility Cookbook by Manuel Matuzovic"
        decoding="async"
        loading="lazy"
    />
</picture>

Any attributes you set on a specific image will overwrite the default config. That brings a lot of flexibility, since you may have cases where you need special optimizations only for some images.

For example, you can use this to generate multiple widths or resolutions for a responsive image:

<img
    src="doggo.jpg"
    width="800"
    alt="a cool dog"
    sizes="(min-width: 940px) 50vw, 100vw"
    eleventy:widths="800,1200"
/>

Here, the custom eleventy:widths attribute will tell the plugin to build a 800px and a 1200px version of this particular image, and insert the correct srcset attributes for it. This is in addition to the avif transform that I opted to do by default. So the final output will look like this:

<picture>
    <source
        sizes="(min-width: 940px) 50vw, 100vw"
        srcset="
            /assets/img/processed/iAm2JcwEED-800.avif   800w,
            /assets/img/processed/iAm2JcwEED-1200.avif 1200w
        "
        type="image/avif"
    />

    <img
        src="/assets/img/processed/iAm2JcwEED-800.jpeg"
        width="800"
        alt="a cool dog"
        sizes="(min-width: 940px) 50vw, 100vw"
        srcset="
            /assets/img/processed/iAm2JcwEED-800.jpg   800w,
            /assets/img/processed/iAm2JcwEED-1200.jpg 1200w
        "
        decoding="async"
        loading="lazy"
    />
</picture>

I ran a quick lighthouse test after I was done and using the image transform knocked my total page weight down even further! Good stuff.

Other Stuff

Permalink to “Other Stuff”

I refactored some other aspects of the site as well - most importantly I switched to Vite for CSS and JS bundling. If you’re interested, you can find everything I did in this pull request.