Skip to content

Instantly share code, notes, and snippets.

@dave-cross
Last active April 13, 2020 23:30
Show Gist options
  • Save dave-cross/fffe82b6a2e9797f663fa77f734ccf98 to your computer and use it in GitHub Desktop.
Save dave-cross/fffe82b6a2e9797f663fa77f734ccf98 to your computer and use it in GitHub Desktop.
A basic Image Sharp transformer for 11ty
const { JSDOM } = require("jsdom");
const responsiveImage = require("./src/js/responsive-image");
// Set the project root for use with Sharp.
global.__rootdir = __dirname;
module.exports = function(eleventyConfig) {
eleventyConfig.addPassthroughCopy("img");
eleventyConfig.addTransform("transform-sharp", async function(
content,
outputPath
) {
const dom = new JSDOM(content);
// Protect 11ty from itself.
// When a code block is used for a transformer and a shorcode
// the transformer might possibly act on the content generated by
// the shortcode. In my case, I probably won't be wrapping images
// in p tags (which markdown does), so a quick child selector
// does the trick.
const images = [...dom.window.document.querySelectorAll("p img")];
for (const image of images) {
const picture = await responsiveImage(
{ src: image.src, alt: image.alt },
outputPath
);
image.insertAdjacentHTML("beforebegin", picture);
image.remove();
}
content = dom.serialize();
return content;
});
// Because it's async, we need a special function.
eleventyConfig.addNunjucksAsyncShortcode("resp", async function(
src,
outputPath,
alt = ""
) {
return await responsiveImage({ src, alt }, outputPath);
});
return {
dir: {
input: "src",
output: "dist"
},
passthroughFileCopy: true
};
};

I've started making an 11ty Sharp transformer. Will need much more work to get it to the level that Gatsby's plugins are working at, and I don't need access to all the inner workings of Sharp so there's a good chance it'll always live as some form of gist for me.

But sharing it around for the curious.

// ./src/js/responsive-image.js
const sharp = require("sharp");
const path = require("path");
module.exports = async function(image, outputPath) {
const { dir: outputDir } = path.parse(outputPath);
const { name: filename, ext } = path.parse(image.src);
let webpSrcSet = "",
origSrcSet = "";
try {
const sharpImage = sharp(path.resolve(__rootdir, outputDir, image.src));
const { width } = await sharpImage.metadata();
const maxWidth = 800;
let outputWidths = [
maxWidth,
maxWidth / 4,
maxWidth / 2,
maxWidth * 1.5,
maxWidth * 2
];
outputWidths = outputWidths
.sort((a, b) => a - b)
.filter(size => size < width);
// In Gatsby's version, we'd also add the original, but
// 11ty passes that image through for us, saving us a step.
for (const outputWidth of outputWidths) {
await sharpImage
.clone()
.resize({ width: outputWidth })
.toFile(
path.resolve(__rootdir, outputDir, `${filename}_${outputWidth}${ext}`)
);
// Need to account for different routes.
origSrcSet += `${filename}_${outputWidth}${ext} ${outputWidth}w,`;
await sharpImage
.clone()
.resize({ width: outputWidth })
.webp()
.toFile(
path.resolve(__rootdir, outputDir, `${filename}_${outputWidth}.webp`)
);
// Need to account for different routes.
webpSrcSet += `${filename}_${outputWidth}.webp ${outputWidth}w,`;
}
} catch (err) {
console.log("nope", err);
return null;
}
return `<picture>
<source srcset="${webpSrcSet}" type="image/webp">
<img src="${filename}${ext}" srcset="${origSrcSet}" alt="${image.alt}"/>
</picture>`;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment