webpack's core repository describes hundreds of APIs, hooks, and plugin interfaces in types.d.ts, but almost none of that reaches readers in a structured or searchable form. The existing documentation site itself had drifted behind the project it documents, both in design and in how quickly it reflects changes.
webpack-doc-kit is the toolchain built to close that gap, and rebuilding it was the subject of webpack's GSoC 2026 project. The pipeline runs end to end: TypeDoc extracts structured data from webpack's type definitions, a custom typedoc-plugin-markdown theme reshapes it into doc-kit-compatible Markdown, and @node-core/doc-kit - the generator behind Node.js's official API documentation - renders the finished site.
The project spanned five milestones and three mentees. My work covered Milestone 2 (Customizing the Output) and Milestone 4 (Core Pages) - the frontend half: giving doc-kit's generic output webpack's visual identity, and building the non-API pages that the generated documentation lives inside. Toward the end of the program I also picked up the local development tooling the repository had been missing.
PR #114 (merged)
@node-core/doc-kit ships a theme whose brand colour variables are named for Node.js's green - --color-green-50 through --color-green-950. webpack's identity is built around Cube Blue. There was no supported way to swap the brand hue without either forking the theme or fighting it at every call site.
The first pass (#108) introduced styles/theme.css with the full webpack palette - the Cube Blue brand ramp plus a neutral ramp for surfaces, type, and borders - and a thin components/Layout.jsx that re-exports doc-kit's DefaultLayout with the theme imported alongside it, wired up through the #theme/Layout import map in doc-kit.config.mjs.
An early revision mapped webpack's greys onto the existing named webpack greys (Concrete, Alto, Dusty Gray, Fiord, Outer Space). Review feedback pushed toward a purpose-built neutral ramp tuned for UI surfaces rather than a direct translation of the old brand swatches.
The second pass (#114) brought in Tailwind CSS v4 and resolved the naming problem properly:
/* styles/theme.css - webpack's real palette */
@theme {
--color-blue-50: #f0f8fe;
/* … */
--color-blue-950: #0a2c47;
}/* styles/index.css - override the inherited Node.js theme variables */
:root {
--color-green-500: var(--color-blue-500);
/* … one alias per stop */
}Every component can now use idiomatic blue-* utilities, while doc-kit's own green-* references resolve to webpack blue.
Getting Tailwind into the build required a custom lightningcss resolver (scripts/html/tailwind.mjs) that compiles CSS on read, caches compiled output by file id, and passes node_modules content through untouched:
const compiler = await compile(code, { base: dirname(id), onDependency: () => {} });
css = compiler.build([]);
cache.set(id, css);The existing Footer stylesheet was converted to @apply in the same PR, cutting it from 90 lines of hand-written CSS to 17.
PR #125 (merged) - closes #121
The generated site had no favicon, no page title, no meta description, and no Open Graph image - every shared link rendered as a bare URL, and the browser tab was unlabelled.
Added the full metadata block to doc-kit.config.mjs: a version-aware title (Webpack v5.x Documentation for versioned API builds, plain Webpack otherwise), a description, an og:image pointing at a new preview asset, and a favicon link.
Deployment-environment handling was tidied up along the way - the base URL had been computed inline at its single use site, and was lifted into a BASE_URL constant so the OG image URL could share it:
export default {
global: {
baseURL: BASE_URL,
},
metadata: {
title: VERSION ? `Webpack ${MAJOR_VERSION} Documentation` : 'Webpack',
head: {
meta: [
{
name: 'description',
content:
'Webpack is the build tool for modern web applications run on NodeJS. Webpack is a module bundler and its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.',
},
{
property: 'og:image',
content: `${BASE_URL}/assets/og_preview.png`,
},
],
links: [
{
rel: 'icon',
href: '/assets/favicon.ico',
},
],
},Since doc-kit only emits generated pages, the build script was extended to copy assets/ into out/ so the favicon and preview image actually ship.
PR #175 (merged) - closes #152
The site had no landing page. Everything beneath /docs existed, but the root was empty.
Four sections, each a component with a colocated CSS module, composed by a layouts/Home layout:
- Hero - headline, primary/secondary calls to action, the webpack logo, and a live stats strip. The release number is read from
#theme/configrather than hard-coded, so it tracks the actual published version. - ConfigSection - a sample
webpack.configbeside a short explanation of loaders and defaults. - FeaturesSection - six cards covering Module Federation, code splitting, tree shaking, HMR, persistent caching, and the plugin ecosystem.
- HomeSponsorSection - tiered sponsor cards plus a backer wall, capped per tier with a "+N more" overflow link through to the full sponsors page.
Syntax highlighting became a documentation feature. The config sample was originally rendered with react-syntax-highlighter. Rather than add a dependency for one code block, the maintainers pursued MDX support in doc-kit upstream (nodejs/doc-kit#845). Once it landed, the home page was restructured so the config lives in pages/index.md as ordinary fenced code blocks with displayName labels - CommonJS, ESM, and TypeScript variants.
export default ({ metadata, children }) => {
return (
<>
<NavBar metadata={metadata} />
{/* rendering hero + configSection described at root index.md*/}
{children}
<FeaturesSection />
<HomeSponsorSection />
<Footer />
</>
);
};PR #172 (merged)
Unmatched routes fell through to the host's default error page - no navigation, no branding, no way back into the docs.
Added layouts/PageNotFound and a four-line pages/404.md that selects it through front-matter:
---
title: 404 - Page Not Found
layout: 404
---The custom layout lives at layouts/PageNotFound and is registered under the 404 key in the central LAYOUTS map in components/Layout.jsx. The front-matter selects it, and the generator emits out/404.html.
PR #181 (merged)
The blog had no machine-readable feed, so readers had no way to subscribe to webpack announcements outside of social media.
Added scripts/rss-feed/index.mjs, a standalone build step that reads every post in pages/blog/posts, parses front-matter with gray-matter, sorts newest-first, and writes RSS 2.0 to out/feed.xml using the feed package:
const posts = files
.map(file => {
const { data } = matter(readFileSync(join(POSTS_DIR, file), 'utf8'));
const postSlug = file.replace(/\.mdx?$/, '');
return {
title: data.title,
link: `${BASE_URL}/blog/posts/${postSlug}`,
date: data.date ? new Date(data.date) : null,
description: data.description || data.title,
};
})
.sort((a, b) => b.date.getTime() - a.date.getTime());It runs as build:rss-feed, which the existing npm-run-all build:* pattern picks up automatically - no change to the build orchestration. An <link rel="alternate" type="application/rss+xml"> was added to the document head so feed readers discover it from any page.
PR #226 - open, changes requested
There was no dev command. Previewing any change meant a full npm run build followed by manually serving out/ - a slow loop for a documentation repository, where most edits are a paragraph in a Markdown file.
scripts/dev/index.mjs runs an initial build, then watches pages/ and the shared directories (components, layouts, styles, hooks, utils, api, public) recursively, and serves out/ locally.
The routing rule is the core of it: a change to a .md or .mdx file triggers a targeted rebuild of just that file via doc-kit's -i/-o flags, with the output path derived from the source path; anything else - a component, a stylesheet, a config file - triggers a full rebuild.
// --- STARTUP ---
console.log('Starting development environment. Running initial build...');
await runDocKit();
console.log('\nWatching directories for changes...');
// Dynamically watch all relevant directories if they exist
const watchDirs = ['pages', ...globalDirs];
for (const dir of watchDirs) {
if (existsSync(`./${dir}`)) {
watch(`./${dir}`, { recursive: true }, (event, filename) =>
handleFileChange(`./${dir}`, filename)
);
}
}
// --- LOCAL SERVER ---
console.log('\nStarting local server...');
const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
spawn(npxCmd, ['serve', './out']);Three points are still open, which is why this PR has not landed: dropping shell: true from the doc-kit invocation (either by removing it or moving to execa); whether native AbortController should replace the tree-kill dependency for cancellation; and a broader question of whether an existing process manager such as pm2 should be doing this work instead. I intend to resolve these and land the PR.
Over the program I opened 10 pull requests against webpack-doc-kit, of which 9 have been merged; the development preview server is the one still in review.
Taken together with the work of my fellow mentees and the guidance of our mentors, the repository moved from a bare doc-kit installation to a modern documentation site: webpack's internal APIs extracted from its own type definitions, rendered through a branded frontend, with the documenting of newly introduced APIs automated rather than done manually.
I would love to keep contributing to the repository and to help maintain it - both by continuing to write code and by reviewing pull requests and supporting new contributors as they find their way into the project.
This project was too large for one contributor, and the milestones in #7 were split three ways. While I worked on Milestones 2 and 4, the rest of the pipeline was built alongside me:
- Mohamed Shams El-Deen completed Milestone 1 (Complete the Theme) - finishing the TypeDoc theme so that webpack's type definitions produce correct, structurally valid Markdown for doc-kit to consume.
- Nikhil Kumar Rajak completed Milestone 3 (CI/CD Integration) and Milestone 5 (Expanding Automation) - automating the full extraction-to-site pipeline and extending it to additional documentation sources, so the site stays current without manual intervention.
Working with them was the best part of the program. The three tracks depended on each other constantly, and reviewing one another's PRs alongside the mentors made the whole thing feel like a team building.
I would also like to thank my mentors @avivkeller, @bjohansebas, and @ovflowd for the review attention and design direction that shaped nearly every PR here, and @evenstensberg and @alexander-akait for helping me land my first pull requests before GSoC began, when I was still new to the webpack organization.