Document Template Generator: Implementation from String Templates to Smart Population
Document Template Generator: Implementation from String Templates to Smart Population#
Writing project documentation is a daily routine for developers—README, CHANGELOG, Issue templates… Each time you copy, paste, and modify a few fields. It’s repetitive and error-prone. Recently, I implemented a document template generator at JsonKit to automate this workflow. The implementation approach is quite interesting, so I’m documenting it here.
The Essence of Template Engines#
At its core, it’s just a function:
const templates = {
readme: {
name: "README.md",
fields: ["projectName", "description", "author", "license"],
generate: (data: Record<string, string>) => `# ${data.projectName}
${data.description}
## Installation
\`\`\`bash
npm install ${data.projectName?.toLowerCase().replace(/\s+/g, '-')}
\`\`\`
`
}
}
Seems simple, but there are several technical details worth noting:
1. Template Literals vs String Concatenation
Early versions used + concatenation:
// Anti-pattern: poor readability, easy to miss quotes
function generate(data) {
return '# ' + data.projectName + '\n\n' +
data.description + '\n\n' +
'## Installation\n\n```bash\n' +
'npm install ' + data.projectName.toLowerCase() + '\n```'
}
Template literals use backticks, support multiline and interpolation, making them far more readable. The key is clear variable interpolation positions—when modifying templates, everything is visible at a glance.
2. Data Transformation Inside Templates
Notice projectName?.toLowerCase().replace(/\s+/g, '-')—this converts “My Project” to “my-project” as a package name. This is more sensible than preprocessing data in the component:
// Approach A: Component preprocessing (not recommended)
const formData = { projectName: "My Project" }
const processedData = {
...formData,
packageName: formData.projectName.toLowerCase().replace(/\s+/g, '-')
}
const output = template.generate(processedData)
// Approach B: Transform inside template (recommended)
const output = template.generate(formData)
// Template handles transformation logic internally
The advantage of Approach B: templates are self-contained. When switching templates, no component code changes are needed, and data structures stay simple.
Dynamic Field Rendering#
Each template has different fields—README needs projectName and author, License only needs author and year. Use configuration-driven rendering:
// Template definition
const templates = {
readme: {
fields: ["projectName", "description", "author", "license"],
// ...
},
license: {
fields: ["author", "year"],
// ...
}
}
// Component dynamic rendering
{template.fields.map((field) => (
<div key={field}>
<label>{field.replace(/([A-Z])/g, " $1").trim()}</label>
<input
value={formData[field] || ""}
onChange={(e) => updateField(field, e.target.value)}
placeholder={`Enter ${field}`}
/>
</div>
))}
The regex field.replace(/([A-Z])/g, " $1") converts camelCase to space-separated: projectName → project Name, making labels more user-friendly.
Real-time Preview and Performance#
Documents are generated in real-time as users type, no “Generate” button needed. The core is React’s state management:
const [formData, setFormData] = useState<Record<string, string>>({})
const [selectedTemplate, setSelectedTemplate] = useState<TemplateKey>("readme")
const template = templates[selectedTemplate]
const generated = template.generate(formData) // Recalculate on every render
Is there a performance issue? Theoretically, every keystroke regenerates the entire document string. But actual testing shows:
- README template generation takes < 1ms
- User input frequency is about 10 times/second
- Even for a large form with 100 fields, computation is millisecond-level
Conclusion: For lightweight document generation, React’s rendering performance is sufficient—no useMemo caching needed.
The truly time-consuming scenario is Blob creation and URL generation during download:
const download = () => {
const blob = new Blob([generated], { type: "text/plain" })
const url = URL.createObjectURL(blob)
const link = document.createElement("a")
link.href = url
link.download = template.name.toLowerCase().replace(/\s+/g, "-")
link.click()
URL.revokeObjectURL(url) // Critical: release memory
}
URL.revokeObjectURL(url) is crucial—without it, multiple downloads cause memory leaks.
Edge Cases Handling#
1. Empty Value Handling
What if a user doesn’t fill in a field? Templates provide default values:
generate: (data) => `# ${data.projectName || "Project Name"}
${data.description || "Project description"}
`
Use the || operator to provide fallback values, avoiding undefined output.
2. Special Characters
What if a project name contains special characters? For example, project<Name> would break Markdown structure:
# project<Name> <!-- <Name> would be treated as HTML tag -->
Need escaping? For document templates, usually no escaping needed because:
- Markdown isn’t sensitive to
<(unless followed by a letter) - User-entered content should be preserved as-is
But if generating HTML or code, you’d need escapeHtml() or JSON.stringify handling.
3. State Persistence on Template Switching
A user fills in projectName for README, switches to License template, then back—will the input still be there?
const [formData, setFormData] = useState<Record<string, string>>({})
// Don't clear formData when switching templates
// When switching back, previously filled values remain
The current implementation doesn’t clear data, which is reasonable—users might switch between templates to compare.
Extensibility Design#
Adding a new template only requires configuration:
const templates = {
// ...existing templates
contributing: {
name: "CONTRIBUTING.md",
fields: ["projectName"],
generate: (data) => `# Contributing Guide
Thank you for your interest in ${data.projectName}!
`
}
}
No component code changes needed—it automatically recognizes the new template’s fields and renders the form. This is a classic application of configuration-driven UI.
Practical Use Cases#
The JsonKit Document Template Generator supports 6 common templates:
- README.md - Project documentation
- API Documentation - REST API endpoint docs
- CHANGELOG.md - Version update logs
- Pull Request Template - PR description template
- Issue Template - Bug report template
- LICENSE - MIT open source license
After generation, one-click copy or download eliminates repetitive work.
Related Tools#
- README Generator - Interactive README creation tool
- Markdown Table Generator - Quickly create Markdown tables
- JSON to Markdown - Convert data to document format
After implementing the document template generator, my biggest takeaway: the simpler the tool, the more technical details it contains. On the surface, it’s string concatenation, but digging deeper reveals considerations in configuration design, state management, and performance optimization. When building tool products, handling these details well creates a smooth user experience.