Halfway through Google Summer of Code 2026 π€¦π»ββοΈ Time flies when you are deeply immersed in AST parsers, metadata extraction, and architectural decisions.
As we're in the GSoC Midterm of redesigning Webpackβs documentation, I will share with you one of my journeys for this phase, It was building the Blog System. It sounded simple at first: create a page, edit some Markdown files, and render them. But in the world of large-scale open-source ecosystems, nothing is just "simple", the first thing you will hit is an architectural decision π
Here is the story of how building a simple blog page led me to contribute core features upstream to nodejs/doc-kit, and the massive engineering lessons I learned along the way.
Challenge 1: The Metadata Transfer βοΈ(Selfishness vs The Ecosystem)
To build a blog, you need metadata: author names, publish dates, and categories.
When I looked into how nodejs/doc-kit (the core engine we use) handled metadata, I found it relied on legacy HTML comments at the top of the file. However, the modern standard across the industry is YAML Frontmatter, something like this:
---
authors: moshams272
---
I stopped for a moment. I could easily write a quick hack in webpack-doc-kitβs codebase to parse YAML and be done with it. But doc-kit will be designed to be a universal tool for multiple projects. If I needed standard YAML, every other project adopting doc-kit would need it too.
The Architectural Decision: Don't be selfish. Fix it from the root.
Instead of adding heavy external dependencies, I implemented a lightweight Pre-AST replacement strategy directly into nodejs/doc-kit. I wrote a parser that intercepts standard YAML blocks and transpiles them into the legacy format before the AST is generated:
const QUERIES = {
// ReGeX for standard Markdown YAML frontmatter
standardYamlFrontmatter: /^---\r?\n([\s\S]*?)\r?\n---/,
};
content
.replace(
QUERIES.standardYamlFrontmatter,
(_, yaml) => '<!-- YAML\n' + yaml + '\n-->\n');
This preserved 100% backward compatibility for existing Node.js docs while unlocking modern YAML support for webpack-doc-kit and future adopters.
PR: Support standard YAML frontmatter
Challenge 2: The Static Assets Copying
A blog isn't a blog without images and covers. But as nodejs/doc-kit moved towards Static Site Generation (SSG), local markdown images were crashing because they weren't being copied to the final build.
Again, the "easy" way out was to write a quick script in webpack-doc-kit to copy the assets. But thinking ecosystem-first, I opened an issue in nodejs/doc-kit.
Issue: Support for static assets copying
This led to a deep architectural discussion with the maintainers. Should the generator automatically detect and copy images?
Or should the user explicitly configure which folders to copy?
We discussed security, performance, and common patterns in tools like Astro.
We decided on adding a pathsToCopy configuration to the Web Generator. I built the utility to parse strings and object mappings, handle ENOENT errors gracefully, and integrate it deeply into the build pipeline.
PR: Implement assets copying in Web Generator
import { cp } from 'node:fs/promises';
import { join, basename } from 'node:path';
import logger from '../../../logger/index.mjs';
/**
* Copies static directories/files defined in `pathsToCopy` to the output directory.
* @param {import('../types').Configuration} config
*/
export async function copyStaticAssets(config) {
if (Array.isArray(config.pathsToCopy)) {
for (const item of config.pathsToCopy) {
if (!item) {
continue;
}
const copyTasks =
typeof item === 'string'
? [{ src: item, dest: join(config.output, basename(item)) }]
: Object.entries(item).map(([src, dest]) => ({
src,
dest: join(config.output, dest.replace(/^[/\\]+/, '')),
}));
for (const { src, dest } of copyTasks) {
try {
await cp(src, dest, { recursive: true, force: true });
} catch (err) {
if (err.code !== 'ENOENT') {
logger.error(
`[web-generator] Failed to copy asset from ${src} to ${dest}: ${err.message}`
);
}
}
}
}
}
}
The Final: Bringing it Home to Webpack
With the roots securely patched in Node.js, building the actual Webpack blog felt like putting together Lego bricks.
I built the data pipeline to sort posts and generate site.json, designed a custom BlogCard with hover effects and nested GitHub avatars, and integrated the entire layout flawlessly into the global Sidebar and Navbar.
That's all with help of my mentors.
PR: Implement new blog layout and pipeline
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import matter from 'gray-matter';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const POSTS_DIR = join(ROOT, 'pages', 'blog', 'posts');
const OUTPUT = join(ROOT, 'generated', 'blog.json');
const toAuthors = data => {
const value = data.authors ?? data.author;
if (Array.isArray(value)) return value.map(String);
if (value == null) return [];
return [String(value)];
};
const titleFromBody = body => body.match(/^#\s+(.+)$/m)?.[1].trim() ?? null;
const readPosts = async () => {
const entries = await readdir(POSTS_DIR);
const files = entries.filter(name => name.endsWith('.md'));
const posts = await Promise.all(
files.map(async file => {
const slug = file.replace(/\.md$/, '');
const { data, content } = matter(
await readFile(join(POSTS_DIR, file), 'utf8')
);
return {
slug,
title: titleFromBody(content) ?? slug,
authors: toAuthors(data),
date: new Date(data.date).toISOString(),
category: data.category ?? null,
image: data.image ?? null,
...(data.description && { description: data.description }),
};
})
);
return posts.sort((a, b) => new Date(b.date) - new Date(a.date));
};
const posts = await readPosts();
await mkdir(dirname(OUTPUT), { recursive: true });
await writeFile(OUTPUT, `${JSON.stringify(posts, null, 2)}\n`);
console.log(`[blog] wrote ${posts.length} posts to ${OUTPUT}`);
The Real GSoC Experience: What I Learned π
Writing code is only 20% of open source. The other 80% completely reshaped how I think as a Software Engineer:
1. "Please Add Tests" π€¦π»ββοΈ
Before GSoC, testing was an afterthought in my side projects. Now, the phrase "please add tests" echoes in my head every time I type. I learned the true value of Test Coverage and Unit Tests. In a massive ecosystem, you cannot just push code and pray. Tests ensure your "fix" in feature X doesn't silently destroy feature Y. Combined with strict formatting and linting rules, I realized that Tests aren't just tooling; they're what make reading and collaborating on enterprise code possible.
2. Architecture over Quick Fixes
In a personal project, making a decision is easy. In an enterprise environment, most choices are a trade-off. Is it fast? Is it secure? Does it break compatibility? π
I learned how to sit back, make the pros and cons balanced, and participate in architectural brainstorming. Itβs not just a side project, itβs an ecosystem.
3. The Open-Source Rhythm (Patience & Boundaries)
When working with mentors across different time zones, patience is key. They are not available 24/7. This taught me an important lesson in work-life balance: It is perfectly fine to work hard for your work days, but you must completely disconnect on your days off, unless it's an emergency π
4. The Upstream-First Mindset
The biggest technical takeaway: If a tool is broken, don't fix the output, fix the tool. Contributing to nodejs/doc-kit to unblock webpack-doc-kit was the most rewarding experience of this phase.
5. Communication is Everything
Open source is built by humans. Discussing with mentors, breaking the ice, sharing a joke, and casually brainstorming ideas with peers and mentors solves 90% of complex problems π€
It has been an incredible Midterm phase. I am excited for the second half of this journey! π₯
Top comments (0)