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](https://img.shields.io/badge/license-${license}-blue.svg)](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](https://img.shields.io/badge/license-${license}-blue.svg)](LICENSE)`

// GitHub Stars badge (requires repo URL)
const starsBadge = github 
  ? `[![GitHub stars](https://img.shields.io/github/stars/${githubPath}.svg?style=social)](${github})`
  : ''

// Issues badge
const issuesBadge = `[![Issues](https://img.shields.io/github/issues/${githubPath}.svg)](${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](https://img.shields.io/badge/license-MIT-blue.svg)](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>

Online tool based on these ideas: README Generator


Related tools: Markdown Table Generator | Code Share