Skip to content

Commit

Permalink
Set up automatic image optimization pipeline (#5779)
Browse files Browse the repository at this point in the history
Use 11ty's first-party [image transformation
plugin](https://www.11ty.dev/docs/plugins/image/) to automatically
optimize images, convert them to `png`, `webp`, and `avif`, and then
transform the site HTML to use the [`<picture>`
element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)
to select the best one for the user. This only runs in production deploy
builds to reduce serving time. Also applies lazy loading and async
decoding to all images, even when serving without optimizations. The
transform also adds a hash of the image, allowing us to expand caching
for images in Firebase hosting.

To account for the transformed HTML structure, some minor HTML and CSS
changes were needed as well.

Overall, this reduces page load time on pages that use images, and
reduces unnecessary downloads that were due to a relatively short cache
time for images. On a page with just a few images, such as
https://dart.dev/tools/pub/automated-publishing, the lighthouse perf
score increases around 10-15 points.

Fixes #4473
Fixes #3124
  • Loading branch information
parlough authored May 29, 2024
1 parent 02853c7 commit bcbff3e
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 29 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
_cache/
_site/
.cache
.*-cache
.*-metadata
.buildlog
Expand Down
37 changes: 35 additions & 2 deletions eleventy.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import yaml from 'js-yaml';

import * as path from 'node:path';
import * as sass from 'sass';
import {eleventyImageTransformPlugin} from '@11ty/eleventy-img';

// noinspection JSUnusedGlobalSymbols
/**
Expand All @@ -27,6 +28,7 @@ import * as sass from 'sass';
*/
export default function (eleventyConfig) {
const isProduction = process.env.PRODUCTION === 'true';
const shouldOptimize = process.env.OPTIMIZE === 'true';

eleventyConfig.on('eleventy.before', async () => {
await configureHighlighting(markdown);
Expand Down Expand Up @@ -71,7 +73,7 @@ export default function (eleventyConfig) {
}

const result = sass.compileString(inputContent, {
style: isProduction ? 'compressed' : 'expanded',
style: shouldOptimize ? 'compressed' : 'expanded',
quietDeps: true,
loadPaths: [parsedPath.dir, 'src/_sass'],
});
Expand Down Expand Up @@ -101,7 +103,7 @@ export default function (eleventyConfig) {
'src/content/guides/language/specifications',
);

if (isProduction) {
if (shouldOptimize) {
// If building for production, minify/optimize the HTML output.
// Doing so during serving isn't worth the extra build time.
eleventyConfig.addTransform('minify-html', async function (content) {
Expand All @@ -118,6 +120,37 @@ export default function (eleventyConfig) {

return content;
});

// Optimize all images, generate an avif, webp, and png version,
// and indicate they should be lazily loaded.
// Save in `_site/assets/img` and update links to there.
eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
extensions: 'html',
formats: ['avif', 'webp', 'png', 'svg'],
svgShortCircuit: true,
widths: ['auto'],
defaultAttributes: {
loading: 'lazy',
decoding: 'async',
},
urlPath: '/assets/img/',
outputDir: '_site/assets/img/',
});
} else {
// To be more consistent with the production build,
// don't optimize images but still indicate they should be lazily loaded.
// Then save in `_site/assets/img` and update links to there.
eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
extensions: 'html',
formats: ['auto'],
widths: ['auto'],
defaultAttributes: {
loading: 'lazy',
decoding: 'async',
},
urlPath: '/assets/img/',
outputDir: '_site/assets/img/',
});
}

eleventyConfig.setQuietMode(true);
Expand Down
9 changes: 8 additions & 1 deletion firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
"trailingSlash": false,
"headers": [
{
"source": "**/*.@(jpg|jpeg|gif|png|md|txt|json|webp|webm|svg|css|js)",
"source": "**/*.@(avif|jpg|jpeg|gif|png|md|txt|json|webp|webm|svg|css|js)",
"headers": [
{ "key": "Cache-Control", "value": "max-age=28800" },
{ "key": "Access-Control-Allow-Origin", "value": "*" }
]
},
{
"source": "/assets/img/*.@(jpg|jpeg|png|webp|avif)",
"headers": [
{ "key": "Cache-Control", "value": "max-age=604800" },
{ "key": "Access-Control-Allow-Origin", "value": "*" }
]
},
{
"source": "**",
"headers": [
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
},
"scripts": {
"serve": "PRODUCTION=false eleventy --serve",
"build-site-for-staging": "PRODUCTION=false eleventy",
"build-site-for-production": "PRODUCTION=true eleventy"
"build-site-for-staging": "PRODUCTION=false OPTIMIZE=true eleventy",
"build-site-for-production": "PRODUCTION=true OPTIMIZE=true eleventy"
},
"engines": {
"node": ">=20.10.0",
Expand All @@ -23,6 +23,7 @@
},
"devDependencies": {
"@11ty/eleventy": "3.0.0-alpha.10",
"@11ty/eleventy-img": "^4.0.2",
"firebase-tools": "^13.10.2",
"hast-util-from-html": "^2.0.1",
"hast-util-select": "^6.0.2",
Expand Down
Loading

0 comments on commit bcbff3e

Please sign in to comment.