Skip to content

Commit 466e078

Browse files
Build perf optimization (#114)
* shared sorting * Webmentions map for faster lookups * Caching images and excerpts * Addressing copilot suggestions
1 parent b9d3f95 commit 466e078

File tree

4 files changed

+122
-66
lines changed

4 files changed

+122
-66
lines changed

.eleventy.js

Lines changed: 30 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -193,73 +193,47 @@ export default async (config) => {
193193
config.addCollection("links", (collectionApi) => {
194194
return collectionApi.getFilteredByGlob("**/links/*.md").reverse();
195195
});
196+
197+
// Optimize: Use a shared sorting function to avoid duplicate sort operations
198+
const sortByDateDesc = (a, b) => b.date - a.date;
199+
196200
config.addCollection("talks", (collectionApi) => {
197-
return (
198-
collectionApi
199-
.getFilteredByGlob("**/talks/*.md")
200-
// sort by date - descending
201-
.sort(function (a, b) {
202-
return b.date - a.date;
203-
})
204-
);
201+
return collectionApi
202+
.getFilteredByGlob("**/talks/*.md")
203+
.sort(sortByDateDesc);
205204
});
206205
config.addCollection("articles", (collectionApi) => {
207-
return (
208-
collectionApi
209-
.getFilteredByGlob("**/articles/*.md")
210-
// sort by date - descending
211-
.sort(function (a, b) {
212-
return b.date - a.date;
213-
})
214-
);
206+
return collectionApi
207+
.getFilteredByGlob("**/articles/*.md")
208+
.sort(sortByDateDesc);
215209
});
216210
config.addCollection("books", (collectionApi) => {
217-
return (
218-
collectionApi
219-
.getFilteredByGlob("**/books/*.md")
220-
// sort by date - descending
221-
.sort(function (a, b) {
222-
return b.date - a.date;
223-
})
224-
);
211+
return collectionApi
212+
.getFilteredByGlob("**/books/*.md")
213+
.sort(sortByDateDesc);
225214
});
226215
config.addCollection("press", (collectionApi) => {
227-
return (
228-
collectionApi
229-
.getFilteredByGlob("**/press/*.md")
230-
// sort by date - descending
231-
.sort(function (a, b) {
232-
return b.date - a.date;
233-
})
234-
);
216+
return collectionApi
217+
.getFilteredByGlob("**/press/*.md")
218+
.sort(sortByDateDesc);
235219
});
236220
config.addCollection("podcasts", (collectionApi) => {
237-
return (
238-
collectionApi
239-
.getFilteredByGlob("**/podcasts/*.md")
240-
// sort by date - descending
241-
.sort(function (a, b) {
242-
return b.date - a.date;
243-
})
244-
);
221+
return collectionApi
222+
.getFilteredByGlob("**/podcasts/*.md")
223+
.sort(sortByDateDesc);
245224
});
246225
config.addCollection("feedAll", (collectionApi) => {
247-
return (
248-
collectionApi
249-
.getFilteredByGlob([
250-
"**/posts/*.md",
251-
"**/links/*.md",
252-
"**/talks/*.md",
253-
"**/articles/*.md",
254-
"**/books/*.md",
255-
"**/podcasts/*.md",
256-
"**/press/*.md",
257-
])
258-
// sort by date - descending
259-
.sort(function (a, b) {
260-
return b.date - a.date;
261-
})
262-
);
226+
return collectionApi
227+
.getFilteredByGlob([
228+
"**/posts/*.md",
229+
"**/links/*.md",
230+
"**/talks/*.md",
231+
"**/articles/*.md",
232+
"**/books/*.md",
233+
"**/podcasts/*.md",
234+
"**/press/*.md",
235+
])
236+
.sort(sortByDateDesc);
263237
});
264238
config.addCollection("sitemap", function (collectionApi) {
265239
// get unsorted items

_11ty/filters.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,27 @@ export default {
251251
},
252252

253253
getWebmentionsForUrl: (webmentions, url, old_url) => {
254+
// Use URL index if available for O(1) lookup instead of O(n) filtering
255+
if (webmentions.urlIndex) {
256+
const mentions = [];
257+
const currentMentions = webmentions.urlIndex.get(url) || [];
258+
mentions.push(...currentMentions);
259+
260+
// Check old URL if provided
261+
if (old_url !== "false") {
262+
const oldMentions = webmentions.urlIndex.get(old_url) || [];
263+
mentions.push(...oldMentions);
264+
}
265+
266+
// Sort by wm-id and remove duplicates
267+
return mentions
268+
.filter((mention, index, self) =>
269+
self.findIndex(m => m["wm-id"] === mention["wm-id"]) === index
270+
)
271+
.sort((a, b) => a["wm-id"] - b["wm-id"]);
272+
}
273+
274+
// Fallback to original filtering method
254275
return webmentions.children
255276
.filter(entry => {
256277
//console.log( entry['wm-target'], url, old_url );

src/_data/webmentions.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ const spammers = JSON.parse(fs.readFileSync(spammers_file));
2121
// Configuration constants for optimization
2222
const MAX_NAME_LENGTH = 200; // Reasonable limit for webmention names to avoid storing extremely long titles
2323

24+
// Create URL-indexed webmentions for faster template lookups
25+
function createWebmentionIndex(webmentions) {
26+
const index = new Map();
27+
28+
webmentions.forEach(mention => {
29+
const target = mention["wm-target"];
30+
if (!index.has(target)) {
31+
index.set(target, []);
32+
}
33+
index.get(target).push(mention);
34+
});
35+
36+
return index;
37+
}
38+
2439
// Optimize webmention data to reduce memory usage
2540
function optimizeWebmention(mention) {
2641
const optimized = {
@@ -188,15 +203,23 @@ export default async function () {
188203
children: mergedChildren.map(optimizeWebmention)
189204
};
190205

191-
return excludeSpammers(optimizedWebmentions);
206+
// Filter spammers first, then create URL index
207+
const cleanWebmentions = excludeSpammers(optimizedWebmentions);
208+
cleanWebmentions.urlIndex = createWebmentionIndex(cleanWebmentions.children);
209+
210+
return cleanWebmentions;
192211
}
193212
}
194213

195-
// Return optimized cache data
214+
// Return optimized cache data with URL index for faster template lookups
196215
const optimizedCache = {
197216
lastFetched: cache.lastFetched,
198217
children: optimizedChildren
199218
};
200219

201-
return excludeSpammers(optimizedCache);
220+
// Filter out spammers first, then create URL index with clean data
221+
const cleanCache = excludeSpammers(optimizedCache);
222+
cleanCache.urlIndex = createWebmentionIndex(cleanCache.children);
223+
224+
return cleanCache;
202225
}

src/posts/posts.11tydata.js

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ dotenv.config();
33

44
import getShareImage from "@jlengstorf/get-share-image";
55

6+
// Cache for generated share images to avoid regeneration
7+
const shareImageCache = new Map();
8+
const MAX_SHARE_IMAGE_CACHE_SIZE = 500; // More than enough for current site
9+
610
// Markdown
711
import markdownIt from "markdown-it";
812
import markdownit_attrs from "markdown-it-attrs";
@@ -17,6 +21,10 @@ const md = markdownIt(markdown_options)
1721
.use(markdownit_attrs)
1822
.use(markdownit_footnote);
1923

24+
// Cache for processed excerpts to avoid re-processing markdown
25+
const excerptCache = new Map();
26+
const MAX_EXCERPT_CACHE_SIZE = 1000; // More than enough for current site
27+
2028
const isDevEnv = process.env.ELEVENTY_ENV === "development";
2129
const todaysDate = new Date();
2230

@@ -62,19 +70,34 @@ export default {
6270
showPost(data) ? `/notebook/${data.page.fileSlug}/` : false,
6371
excerpt: (data) => {
6472
let excerpt = "";
73+
let sourceText = "";
74+
6575
if ("excerpt" in data.page) {
66-
excerpt = md
67-
.renderInline(data.page.excerpt)
68-
.replace(/\[\^\d+\]/gi, "") // remove footnotes
69-
.replace(/(<([^>]+)>)/gi, "") // remove HTML
70-
.trim();
76+
sourceText = data.page.excerpt;
7177
} else if (data.description) {
78+
sourceText = data.description;
79+
}
80+
81+
if (sourceText) {
82+
// Check cache first
83+
if (excerptCache.has(sourceText)) {
84+
return excerptCache.get(sourceText);
85+
}
86+
7287
excerpt = md
73-
.renderInline(data.description)
88+
.renderInline(sourceText)
7489
.replace(/\[\^\d+\]/gi, "") // remove footnotes
7590
.replace(/(<([^>]+)>)/gi, "") // remove HTML
7691
.trim();
92+
93+
// Cache the processed excerpt with size limit
94+
if (excerptCache.size >= MAX_EXCERPT_CACHE_SIZE) {
95+
const firstKey = excerptCache.keys().next().value;
96+
excerptCache.delete(firstKey);
97+
}
98+
excerptCache.set(sourceText, excerpt);
7799
}
100+
78101
return excerpt;
79102
},
80103
hue: (data) => {
@@ -84,7 +107,14 @@ export default {
84107
if (data.hero) {
85108
return `${data.site.url}${data.hero.src}`;
86109
} else {
87-
return getShareImage({
110+
// Create cache key from title + tags to avoid regenerating identical images
111+
const cacheKey = `${data.title}-${tagsToString(data.tags)}`;
112+
113+
if (shareImageCache.has(cacheKey)) {
114+
return shareImageCache.get(cacheKey);
115+
}
116+
117+
const shareImage = getShareImage({
88118
cloudName: "aarongustafson",
89119
imagePublicID: "share-card",
90120
tagline: tagsToString(data.tags),
@@ -107,6 +137,14 @@ export default {
107137
titleBottomOffset: 205,
108138
textColor: "2C2825",
109139
});
140+
141+
// Cache with size limit
142+
if (shareImageCache.size >= MAX_SHARE_IMAGE_CACHE_SIZE) {
143+
const firstKey = shareImageCache.keys().next().value;
144+
shareImageCache.delete(firstKey);
145+
}
146+
shareImageCache.set(cacheKey, shareImage);
147+
return shareImage;
110148
}
111149
},
112150
},

0 commit comments

Comments
 (0)