From Template Strings to Standard Practices: Building a README Generator
From Template Strings to Standard Practices: Building a README Generator#
Project is done. Now, how to write the README? Many developers (including myself) have been there: the code is solid, but the README is an afterthought. It’s not about lack of motivation—it’s the repetitive work of structuring sections, finding badges, formatting text.
Recently added a README generator to JsonKit. Here’s the technical breakdown.
The Standard README Structure#
Let’s look at what mature READMEs include:
# Project Name
Project description
[Badges: License, Stars, Issues, etc.]
## Features
## Installation
## Usage
## API Reference
## Contributing
## License
This structure has become a de facto standard. The problem is writing it from scratch every time.
Core Approach: Form-Driven Template Assembly#
The core idea is form input + template concatenation. Users fill a form, the program generates content based on a template:
function generateReadme(formData: FormData): string {
const { projectName, description, features, installation, license } = formData
let readme = ''
// Title
if (projectName) {
readme += `# ${projectName}\n\n`
}
// Description
if (description) {
readme += `${description}\n\n`
}
// Badges
readme += `[](LICENSE)\n`
// Features (multiline handling)
if (features) {
readme += `## ✨ Features\n\n`
const featureList = features.split('\n').filter(f => f.trim())
featureList.forEach(feature => {
readme += `- ${feature.trim()}\n`
})
}
return readme
}
Simple, but there are details worth discussing.
String Handling Edge Cases#
1. Empty Value Filtering#
Users might leave fields empty. Don’t let the template show empty sections:
// ❌ Bad
readme += `## Installation\n\n${installation}\n` // Ugly when empty
// ✅ Good
if (installation) {
readme += `## 🚀 Installation\n\n\`\`\`bash\n${installation}\n\`\`\`\n\n`
} else {
// Provide default template
readme += `## 🚀 Installation\n\n\`\`\`bash\nnpm install ${projectName.toLowerCase()}\n\`\`\`\n\n`
}
2. Multiline Text Processing#
Feature input might span multiple lines. Parse and format each line:
const featureList = features
.split('\n') // Split by newline
.map(f => f.trim()) // Trim whitespace
.filter(f => f.length > 0) // Remove empty lines
.map(f => `- ${f}`) // Add list markers
readme += featureList.join('\n')
3. Project Name Normalization#
Project names might contain spaces, uppercase letters. Convert to npm package format:
function normalizePackageName(name: string): string {
return name
.toLowerCase()
.replace(/\s+/g, '-') // Spaces to hyphens
.replace(/[^a-z0-9-]/g, '') // Remove special chars
}
// "My Cool Project" → "my-cool-project"
Badge Generation: Dynamic SVGs#
Those common badges (License, Stars, Issues) in READMEs come from shields.io. This service provides dynamically generated SVGs:
// License badge
const licenseBadge = `[](LICENSE)`
// GitHub Stars badge (requires repo URL)
const starsBadge = github
? `[](${github})`
: ''
// Issues badge
const issuesBadge = `[](${github}/issues)`
The shields.io API format is:
https://img.shields.io/badge/<label>-<message>-<color>.svg
label: Left text (e.g., “license”)message: Right text (e.g., “MIT”)color: Color (blue, green, red, etc.)
License Selection and SPDX Identifiers#
Common open-source licenses include MIT, Apache 2.0, GPL 3.0. We use SPDX identifiers for standardization:
const LICENSES = {
'MIT': {
name: 'MIT License',
spdx: 'MIT',
badge: 'MIT'
},
'Apache-2.0': {
name: 'Apache License 2.0',
spdx: 'Apache-2.0',
badge: 'Apache%202.0' // URL encoded
},
'GPL-3.0': {
name: 'GNU General Public License v3.0',
spdx: 'GPL-3.0',
badge: 'GPL%20v3'
}
}
SPDX (Software Package Data Exchange) is a Linux Foundation standard, widely supported by npm, GitHub, and other platforms.
Export Feature: Blob API#
Generated content needs to be downloadable. Use the Blob API:
function handleDownload(content: string) {
const blob = new Blob([content], { type: 'text/markdown' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'README.md'
a.click()
URL.revokeObjectURL(url) // Free memory
}
Note that URL.revokeObjectURL() is not optional—it prevents memory leaks.
Real-time Preview Implementation#
Show users the result as they type. Use React controlled components:
const [formData, setFormData] = useState(initialFormData)
const [preview, setPreview] = useState('')
// Regenerate when form changes
useEffect(() => {
setPreview(generateReadme(formData))
}, [formData])
// Input field
<input
value={formData.projectName}
onChange={(e) => setFormData(prev => ({
...prev,
projectName: e.target.value
}))}
/>
Every keystroke triggers regeneration, but README generation is pure string manipulation—performance isn’t an issue. For more complex templates, add debounce:
const debouncedGenerate = useMemo(
() => debounce((data) => generateReadme(data), 300),
[]
)
useEffect(() => {
debouncedGenerate(formData)
}, [formData])
Generated Example#
Form input:
Project Name: JsonKit
Description: An open-source JSON toolkit
Features:
JSON formatting
JSON minification
JSON validation
Installation: npm install jsonkit-tools
License: MIT
Generated output:
# JsonKit
An open-source JSON toolkit
[](LICENSE)
## ✨ Features
- JSON formatting
- JSON minification
- JSON validation
## 🚀 Installation
\`\`\`bash
npm install jsonkit-tools
\`\`\`
## 📄 License
This project is licensed under the MIT License.
Future Improvements#
Current implementation uses simple string concatenation. Here’s how to make it more powerful:
1. Support Custom Templates#
const template = `
# {{projectName}}
{{description}}
## Installation
\`\`\`bash
{{installation}}
\`\`\`
`
function render(template: string, data: Record<string, string>) {
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] || '')
}
2. Markdown Linting#
Validate generated content with a linter:
import { lint } from 'markdownlint'
const result = lint(generatedReadme)
if (result.length > 0) {
console.warn('Markdown issues:', result)
}
3. Preview Rendering#
Use react-markdown to render generated Markdown as HTML preview:
import ReactMarkdown from 'react-markdown'
<ReactMarkdown>{generatedReadme}</ReactMarkdown>
Tool Links#
Online tool based on these ideas: README Generator
Related tools: Markdown Table Generator | Code Share