Fix npm audit vulnerabilities and broken image build
Build and copy to prod / build-and-copy (push) Successful in 4m15s
Build and copy to prod / build-and-copy (push) Successful in 4m15s
Eleventy Image v7 removed its synchronous stats API, which broke the build. Rather than downgrade (older versions bundle a vulnerable sharp), adapt to v7 and clear out the remaining vulnerable dependencies: - Make the `image` shortcode async - Replace the unmaintained markdown-it-eleventy-img with a local plugin that pre-loads image metadata in eleventy.before, so markdown (and feed) images keep rendering as <picture> elements - Upgrade @11ty/eleventy-fetch to v6 - Swap npm-run-all for the maintained npm-run-all2 fork - Override eleventy-plugin-og-image's satori (and its pinned fflate) to patched versions, and set explicit heading weights in the OG template so newer satori doesn't synthesise bold text
This commit is contained in:
1 parent
13d0b244e5
commit
f45b6308f9
6 files changed
+284
-2427
No files matched your search
@@ -9,15 +9,14 @@ const defaultSizes = `
|
||||
(min-width: 2500px) 3840px
|
||||
`;
|
||||
|
||||
const imageShortcode = (src, alt, cls, sizes = defaultSizes, widths = [300, 600, 1000, 1440, 2500, 3840]) => {
|
||||
const imageShortcode = async (src, alt, cls, sizes = defaultSizes, widths = [300, 600, 1000, 1440, 2500, 3840]) => {
|
||||
let options = {
|
||||
widths: widths,
|
||||
formats: ['webp', 'jpeg'],
|
||||
outputDir: "./_site/img",
|
||||
};
|
||||
|
||||
// generate images, while this is async we don’t wait
|
||||
Image(src, options);
|
||||
let metadata = await Image(src, options);
|
||||
|
||||
let imageAttributes = {
|
||||
class: cls,
|
||||
@@ -25,8 +24,6 @@ const imageShortcode = (src, alt, cls, sizes = defaultSizes, widths = [300, 600,
|
||||
sizes,
|
||||
decoding: "async",
|
||||
};
|
||||
// get metadata even the images are not fully generated
|
||||
let metadata = Image.statsSync(src, options);
|
||||
return Image.generateHTML(metadata, imageAttributes);
|
||||
}
|
||||
|
||||
@@ -49,6 +46,6 @@ const remoteImageShortcode = async (src, alt, cls, sizes = defaultSizes, widths
|
||||
return Image.generateHTML(metadata, imageAttributes);
|
||||
}
|
||||
export default function(eleventyConfig) {
|
||||
eleventyConfig.addShortcode("image", imageShortcode);
|
||||
eleventyConfig.addAsyncShortcode("image", imageShortcode);
|
||||
eleventyConfig.addAsyncShortcode("remoteImage", remoteImageShortcode);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import Image from "@11ty/eleventy-img";
|
||||
|
||||
// Some files in src/images have a literal query string in their filename (e.g. `foo.jpeg?width=300`)
|
||||
const IMAGE_EXTENSIONS = /\.(jpe?g|png|webp|gif|avif|svg|tiff?)(\?.*)?$/i;
|
||||
|
||||
/**
|
||||
* Renders local markdown images as responsive <picture> elements via Eleventy Image.
|
||||
*
|
||||
* Replaces `markdown-it-eleventy-img`, which is unmaintained and pinned to an old
|
||||
* (vulnerable) Eleventy Image + sharp. Eleventy Image v7 dropped its synchronous
|
||||
* stats API, but markdown-it renders synchronously, so image metadata is read
|
||||
* up-front in `eleventy.before` and looked up while rendering. Images are only
|
||||
* generated once a page actually references them, and `eleventy.after` waits for
|
||||
* those to finish.
|
||||
*
|
||||
* Remote images are passed through to markdown-it's default renderer untouched.
|
||||
*/
|
||||
export function createMarkdownImages({ imageDir, imgOptions = {}, globalAttributes = {} }) {
|
||||
const metadataByPath = new Map();
|
||||
let pendingGenerations = [];
|
||||
|
||||
async function loadMetadata() {
|
||||
const entries = await fs.readdir(imageDir, { recursive: true, withFileTypes: true });
|
||||
const files = entries
|
||||
.filter(entry => entry.isFile() && IMAGE_EXTENSIONS.test(entry.name))
|
||||
.map(entry => path.join(entry.parentPath, entry.name));
|
||||
|
||||
await Promise.all(files.map(async file => {
|
||||
const metadata = await Image(file, { ...imgOptions, statsOnly: true });
|
||||
metadataByPath.set(path.normalize(file), metadata);
|
||||
}));
|
||||
}
|
||||
|
||||
async function waitForGenerations() {
|
||||
const generations = pendingGenerations;
|
||||
pendingGenerations = [];
|
||||
await Promise.all(generations);
|
||||
}
|
||||
|
||||
function markdownItPlugin(md) {
|
||||
md.renderer.rules.image = (tokens, index, rendererOptions, env, renderer) => {
|
||||
const token = tokens[index];
|
||||
const src = token.attrGet("src");
|
||||
|
||||
if (Image.Util.isRemoteUrl(src)) {
|
||||
token.attrSet("alt", token.content);
|
||||
return renderer.renderToken(tokens, index, rendererOptions);
|
||||
}
|
||||
|
||||
const filePath = path.normalize(decodeURIComponent(src));
|
||||
const metadata = metadataByPath.get(filePath);
|
||||
if (!metadata) {
|
||||
throw new Error(`Markdown image "${src}" was not found in ${imageDir}. Local markdown images must live in that directory.`);
|
||||
}
|
||||
|
||||
pendingGenerations.push(Image(filePath, imgOptions));
|
||||
|
||||
const tokenAttributes = Object.fromEntries(
|
||||
token.attrs
|
||||
.filter(([name]) => name !== "src")
|
||||
.map(([name, value]) => [name.toLowerCase().trim(), value])
|
||||
);
|
||||
|
||||
return Image.generateHTML(metadata, {
|
||||
...globalAttributes,
|
||||
...tokenAttributes,
|
||||
alt: token.content,
|
||||
}, {
|
||||
whitespaceMode: "inline"
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function eleventyPlugin(eleventyConfig) {
|
||||
eleventyConfig.on("eleventy.before", loadMetadata);
|
||||
eleventyConfig.on("eleventy.after", waitForGenerations);
|
||||
}
|
||||
|
||||
return { markdownItPlugin, eleventyPlugin };
|
||||
}
|
||||
+20
-16
@@ -9,12 +9,29 @@ import { full as markdownItEmoji } from 'markdown-it-emoji';
|
||||
import markdownItFootnote from 'markdown-it-footnote';
|
||||
import markdownitMark from 'markdown-it-mark';
|
||||
import markdownitAbbr from 'markdown-it-abbr';
|
||||
import markdownItEleventyImg from 'markdown-it-eleventy-img';
|
||||
import markdownItCallouts from 'markdown-it-obsidian-callouts';
|
||||
import { slugifyString } from '../utils.js';
|
||||
import { createMarkdownImages } from './markdown-it-image.js';
|
||||
|
||||
const icons = JSON.parse(fs.readFileSync('./config/plugins/icons.json'), 'utf-8');
|
||||
|
||||
const markdownImages = createMarkdownImages({
|
||||
imageDir: path.join("src", "images"),
|
||||
imgOptions: {
|
||||
widths: [300, 600, 1000, 1440],
|
||||
urlPath: "/img/",
|
||||
outputDir: path.join("_site", "img"),
|
||||
formats: ["avif", "webp", "jpeg"]
|
||||
},
|
||||
globalAttributes: {
|
||||
sizes: `(max-width: 300px) 300px,
|
||||
(max-width: 600px) 600px,
|
||||
(max-width: 1000px) 1000px,
|
||||
(max-width: 1440px) 1440px,
|
||||
100%`
|
||||
}
|
||||
});
|
||||
|
||||
const markdownLib = markdownIt({
|
||||
html: true,
|
||||
breaks: true,
|
||||
@@ -53,27 +70,14 @@ const markdownLib = markdownIt({
|
||||
.use(markdownitMark)
|
||||
.use(markdownitAbbr)
|
||||
.use(markdownItCallouts)
|
||||
.use(markdownItEleventyImg, {
|
||||
imgOptions: {
|
||||
widths: [300, 600, 1000, 1440],
|
||||
urlPath: "/img/",
|
||||
outputDir: path.join("_site", "img"),
|
||||
formats: ["avif", "webp", "jpeg"]
|
||||
},
|
||||
globalAttributes: {
|
||||
sizes: `(max-width: 300px) 300px,
|
||||
(max-width: 600px) 600px,
|
||||
(max-width: 1000px) 1000px,
|
||||
(max-width: 1440px) 1440px,
|
||||
100%`
|
||||
}
|
||||
});
|
||||
.use(markdownImages.markdownItPlugin);
|
||||
|
||||
markdownLib.renderer.rules.emoji = function(token, idx) {
|
||||
return `<span class="fa-li"><i class="fa-solid fa-${token[idx].markup}" aria-hidden="true"></i></span>`;
|
||||
};
|
||||
|
||||
export default function(eleventyConfig) {
|
||||
eleventyConfig.addPlugin(markdownImages.eleventyPlugin);
|
||||
eleventyConfig.addFilter('md', content => content ? markdownLib.render(content) : "");
|
||||
eleventyConfig.setLibrary('md', markdownLib);
|
||||
}
|
||||
Generated
+166
-2398
File diff suppressed because it is too large.
Load diff
+10
-4
@@ -21,7 +21,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@11ty/eleventy": "^3.0.0",
|
||||
"@11ty/eleventy-fetch": "^3.0.0",
|
||||
"@11ty/eleventy-fetch": "^6.0.1",
|
||||
"@11ty/eleventy-img": "^7.1.1",
|
||||
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
|
||||
"@lewisdale/blog-post-cli": "^1.0.3",
|
||||
@@ -33,14 +33,13 @@
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-abbr": "^2.0.0",
|
||||
"markdown-it-anchor": "^9.2.0",
|
||||
"markdown-it-eleventy-img": "^0.10.2",
|
||||
"markdown-it-emoji": "^3.0.0",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"markdown-it-link-attributes": "^4.0.1",
|
||||
"markdown-it-mark": "^4.0.0",
|
||||
"markdown-it-obsidian-callouts": "^0.2.6",
|
||||
"markdown-it-prism": "^2.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"npm-run-all2": "^9.0.4",
|
||||
"p-map": "^7.0.2",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-cli": "^10.1.0",
|
||||
@@ -57,7 +56,14 @@
|
||||
},
|
||||
"allowScripts": {
|
||||
"sharp@0.35.5": false,
|
||||
"sharp@0.33.5": false,
|
||||
"fsevents@2.3.2": false
|
||||
},
|
||||
"overrides": {
|
||||
"eleventy-plugin-og-image": {
|
||||
"satori": {
|
||||
".": "^0.63.2",
|
||||
"fflate": "^0.7.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<div style="height: 100%; width: 100%; display: flex; align-items: stretch; background-color: #262626;">
|
||||
<div style="display: flex; flex-direction: column; flex-grow: 1; align-items: center; margin: 80px; background-color: #262626;">
|
||||
<div style="display: flex; flex-grow: 1; flex-direction: column; align-items: center; justify-content: center; font-family: 'Space Grotesk'; font-weight: 500; color: #e5e5e5; text-align: center;">
|
||||
<h1 style="font-size: 80px;">{{ title | safe }}</h1>
|
||||
<h2 style="font-size: 44px;">{{ subTitle | safe }}</h2>
|
||||
<h1 style="font-size: 80px; font-weight: 500;">{{ title | safe }}</h1>
|
||||
<h2 style="font-size: 44px; font-weight: 500;">{{ subTitle | safe }}</h2>
|
||||
</div>
|
||||
<div style="display: flex; justify-self: flex-end; flex-grow: 0; font-family: 'Space Grotesk'; font-weight: 500; color: #e5e5e5; text-align: center;">
|
||||
<h3 style="font-size: 20px;">LewisDale.dev</h3>
|
||||
<h3 style="font-size: 20px; font-weight: 500;">LewisDale.dev</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in new issue
Block a user