From f0c4f873d320888bde486aa735068b282281bfad Mon Sep 17 00:00:00 2001 From: zhravan Date: Thu, 12 Feb 2026 14:27:47 +0530 Subject: [PATCH 1/5] feat: add RSS feed for blog --- src/scripts/generate-rss.mjs | 111 +++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/scripts/generate-rss.mjs diff --git a/src/scripts/generate-rss.mjs b/src/scripts/generate-rss.mjs new file mode 100644 index 000000000000..51e44c5520d4 --- /dev/null +++ b/src/scripts/generate-rss.mjs @@ -0,0 +1,111 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, "../.."); +const BASE_URL = "https://webpack.js.org"; + +function escapeXml(text) { + return String(text) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function extractPubDate(node) { + const name = node.name || ""; + + // 1. Filename pattern YYYY-MM-DD-* + const filenameDateMatch = name.match(/^(\d{4}-\d{2}-\d{2})-/); + if (filenameDateMatch) { + const d = new Date(filenameDateMatch[1]); + if (!Number.isNaN(d.getTime())) return d; + } + + // 2. Parse from title (YYYY-MM-DD) + const title = node.title || ""; + const titleDateMatch = title.match(/\((\d{4}-\d{2}-\d{2})\)/); + if (titleDateMatch) { + const d = new Date(titleDateMatch[1]); + if (!Number.isNaN(d.getTime())) return d; + } + + // 3. Fallback: file mtime + const filePath = path.resolve(ROOT, node.path); + try { + const stat = fs.statSync(filePath); + return stat.mtime; + } catch { + return new Date(); + } +} + +function formatRfc2822(date) { + return date.toUTCString(); +} + +function main() { + const contentPath = path.resolve(ROOT, "src/_content.json"); + const contentTree = JSON.parse(fs.readFileSync(contentPath, "utf8")); + + const blogSection = contentTree.children?.find( + (child) => child.name === "blog" && child.type === "directory", + ); + + if (!blogSection?.children) { + console.error("generate-rss: blog section not found in content tree"); + process.exit(1); + } + + const posts = blogSection.children + .filter( + (child) => + child.type === "file" && + (child.extension === ".mdx" || child.extension === ".md") && + child.name !== "index" && + child.name !== "printable" && + child.url !== "/blog/", + ) + .map((node) => ({ + title: node.title || "Untitled", + link: `${BASE_URL}${node.url}`, + pubDate: extractPubDate(node), + description: node.description || node.title || "Untitled", + })) + .sort((a, b) => b.pubDate.getTime() - a.pubDate.getTime()); + + const lastBuildDate = formatRfc2822(new Date()); + + const xml = ` + + + webpack Blog + ${BASE_URL}/blog/ + Announcements and updates from the webpack team + en + ${escapeXml(lastBuildDate)} + +${posts + .map( + (post) => ` + ${escapeXml(post.title)} + ${escapeXml(post.link)} + ${escapeXml(post.description)} + ${escapeXml(formatRfc2822(post.pubDate))} + ${escapeXml(post.link)} + `, + ) + .join("\n")} + + +`; + + const distPath = path.resolve(ROOT, "dist/feed.xml"); + fs.writeFileSync(distPath, xml, "utf8"); + console.log(`Successfully generated RSS feed at ${distPath}`); +} + +main(); From 678c013af6be9f35b2140d6105ee3b5e91f5ba55 Mon Sep 17 00:00:00 2001 From: zhravan Date: Thu, 12 Feb 2026 14:28:22 +0530 Subject: [PATCH 2/5] feat: site reference to xml feed for blogs --- src/components/Site/Site.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/Site/Site.jsx b/src/components/Site/Site.jsx index 523a9d4742c7..2736a344aad6 100644 --- a/src/components/Site/Site.jsx +++ b/src/components/Site/Site.jsx @@ -199,6 +199,12 @@ function Site(props) { location.pathname, )}`} /> + From 785eec8207d6ce5fdd98c5f4871045c97302405c Mon Sep 17 00:00:00 2001 From: zhravan Date: Thu, 12 Feb 2026 14:28:42 +0530 Subject: [PATCH 3/5] feat: include it as part of postbuild CMD --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2472d23c3e13..f15aaab9cb58 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "fetch-all": "run-s fetch-repos fetch", "prebuild": "npm run clean", "build": "run-s fetch-repos fetch:governance fetch content && webpack --config webpack.prod.mjs --config-node-env production && run-s printable content && webpack --config webpack.ssg.mjs --config-node-env production --env ssg", - "postbuild": "npm run sitemap", + "postbuild": "npm run sitemap && npm run rss", "build-test": "npm run build && http-server --port 4200 dist/", "serve-dist": "http-server --port 4200 dist/", "test": "npm run lint", @@ -47,6 +47,7 @@ "lint:prose": "vale --config='.vale.ini' src/content", "lint:links": "hyperlink -c 8 --root dist -r dist/index.html --canonicalroot https://webpack.js.org/ --internal --skip /plugins/extract-text-webpack-plugin/ --skip /printable --skip /contribute/Governance --skip https:// --skip http:// --skip sw.js --skip /vendor > internal-links.tap; cat internal-links.tap | tap-spot", "sitemap": "cd dist && sitemap-static --ignore-file=../sitemap-ignore.json --pretty --prefix=https://webpack.js.org/ > sitemap.xml", + "rss": "node src/scripts/generate-rss.mjs", "serve": "npm run build && sirv start ./dist --port 4000", "preprintable": "npm run clean-printable", "printable": "node ./src/scripts/concatenate-docs.mjs", From 8ef932dfbc6fcc40e36dd1808921c927ce16e35e Mon Sep 17 00:00:00 2001 From: zhravan Date: Thu, 12 Feb 2026 14:55:33 +0530 Subject: [PATCH 4/5] feat: self review --- eslint.config.mjs | 1 + src/scripts/generate-rss.mjs | 26 +++++++++++--------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 0552609c2126..7fc04bdf812c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -8,6 +8,7 @@ export default defineConfig([ globalIgnores([ "**/dist/", "**/examples/", + "**/printable.mdx", "src/content/loaders/_*.mdx", "src/content/plugins/_*.mdx", "src/content/contribute/Governance-*.mdx", diff --git a/src/scripts/generate-rss.mjs b/src/scripts/generate-rss.mjs index 51e44c5520d4..f3cbe5501248 100644 --- a/src/scripts/generate-rss.mjs +++ b/src/scripts/generate-rss.mjs @@ -8,32 +8,29 @@ const BASE_URL = "https://webpack.js.org"; function escapeXml(text) { return String(text) - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); } function extractPubDate(node) { const name = node.name || ""; - // 1. Filename pattern YYYY-MM-DD-* const filenameDateMatch = name.match(/^(\d{4}-\d{2}-\d{2})-/); if (filenameDateMatch) { - const d = new Date(filenameDateMatch[1]); - if (!Number.isNaN(d.getTime())) return d; + const date = new Date(filenameDateMatch[1]); + if (!Number.isNaN(date.getTime())) return date; } - // 2. Parse from title (YYYY-MM-DD) const title = node.title || ""; const titleDateMatch = title.match(/\((\d{4}-\d{2}-\d{2})\)/); if (titleDateMatch) { - const d = new Date(titleDateMatch[1]); - if (!Number.isNaN(d.getTime())) return d; + const date = new Date(titleDateMatch[1]); + if (!Number.isNaN(date.getTime())) return date; } - // 3. Fallback: file mtime const filePath = path.resolve(ROOT, node.path); try { const stat = fs.statSync(filePath); @@ -56,8 +53,7 @@ function main() { ); if (!blogSection?.children) { - console.error("generate-rss: blog section not found in content tree"); - process.exit(1); + throw new Error("generate-rss: blog section not found in content tree"); } const posts = blogSection.children @@ -75,7 +71,7 @@ function main() { pubDate: extractPubDate(node), description: node.description || node.title || "Untitled", })) - .sort((a, b) => b.pubDate.getTime() - a.pubDate.getTime()); + .toSorted((a, b) => b.pubDate.getTime() - a.pubDate.getTime()); const lastBuildDate = formatRfc2822(new Date()); From 354d773268548b0dea91bf4c3dcf9445d9e665b7 Mon Sep 17 00:00:00 2001 From: zhravan Date: Sat, 14 Feb 2026 22:53:00 +0530 Subject: [PATCH 5/5] feat(review): exclude blog index and printable from feed --- src/scripts/generate-rss.mjs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/scripts/generate-rss.mjs b/src/scripts/generate-rss.mjs index f3cbe5501248..a6595aee3820 100644 --- a/src/scripts/generate-rss.mjs +++ b/src/scripts/generate-rss.mjs @@ -61,9 +61,12 @@ function main() { (child) => child.type === "file" && (child.extension === ".mdx" || child.extension === ".md") && - child.name !== "index" && - child.name !== "printable" && - child.url !== "/blog/", + child.name !== "index.mdx" && + child.name !== "index.md" && + child.name !== "printable.mdx" && + child.name !== "printable.md" && + child.url !== "/blog/" && + !child.url?.includes("/printable"), ) .map((node) => ({ title: node.title || "Untitled",