JavaScript/React/Node Documentation Integration Guide
This guide explains how to integrate JavaScript, React, and Node.js repositories into the central documentation system using JSDoc comments and jsdoc-to-markdown.
Table of Contents
- Overview
- JSDoc vs XML Comments
- Setup and Configuration
- Writing JSDoc Comments
- Per-File Documentation Generation
- Pipeline Integration
- Examples
- Best Practices
Overview
Architecture for JavaScript Repositories
┌─────────────────────────────────────────────────────────────────┐
│ JAVASCRIPT DOCUMENTATION FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. SOURCE LAYER │
│ ├── JSDoc Comments in .js/.jsx files │
│ └── Markdown articles in /docs │
│ ↓ │
│ 2. BUILD LAYER │
│ ├── jsdoc-to-markdown extracts JSDoc → Markdown │
│ ├── Per-file API documentation generated │
│ ├── Custom script aggregates into /api folder │
│ └── DocFX builds complete site │
│ ↓ │
│ 3. DISTRIBUTION LAYER (Same as C#) │
│ ├── Indexes to Elasticsearch │
│ ├── Pushes to Central Repository │
│ └── Deploys to Azure │
│ ↓ │
│ 4. ACCESS LAYER (Same as C#) │
│ ├── Web Browser │
│ ├── MCP Server │
│ └── Elasticsearch │
│ │
└─────────────────────────────────────────────────────────────────┘
Key Differences from C# Repos
| Aspect | C# (DocFX) | JavaScript (JSDoc) |
|---|---|---|
| Comment Style | XML (///) | JSDoc (/** */) |
| Extraction Tool | DocFX metadata | jsdoc-to-markdown |
| Output Format | YAML | Markdown |
| Per-File Docs | No | Yes (recommended) |
| Build Process | Single command | Multi-step script |
JSDoc vs XML Comments
C# XML Comments
/// <summary>
/// Processes a user request
/// </summary>
/// <param name="userId">The user ID</param>
/// <returns>Processing result</returns>
public async Task<Result> ProcessUser(string userId)
JavaScript JSDoc Comments
/**
* Processes a user request
* @param {string} userId - The user ID
* @returns {Promise<Result>} Processing result
* @async
*/
async function processUser(userId) {
// Implementation
}
Setup and Configuration
1. Install Dependencies
package.json:
{
"name": "your-project",
"version": "1.0.0",
"scripts": {
"docs:generate": "node scripts/generate-docs.js",
"docs:build": "npm run docs:generate && docfx build docfx.json",
"docs:serve": "docfx serve _site"
},
"devDependencies": {
"jsdoc-to-markdown": "^8.0.0",
"glob": "^10.3.10",
"fs-extra": "^11.2.0"
}
}
Install:
npm install --save-dev jsdoc-to-markdown glob fs-extra
2. Create Documentation Generation Script
scripts/generate-docs.js:
const jsdoc2md = require('jsdoc-to-markdown');
const fs = require('fs-extra');
const path = require('path');
const glob = require('glob');
/**
* Configuration
*/
const config = {
sourceDir: 'src',
outputDir: 'api',
filePatterns: ['**/*.js', '**/*.jsx'],
excludePatterns: [
'**/node_modules/**',
'**/*.test.js',
'**/*.spec.js',
'**/dist/**',
'**/build/**'
],
templateFile: 'scripts/api-template.hbs'
};
/**
* Generate documentation for a single file
* @param {string} filePath - Path to source file
* @returns {Promise<void>}
*/
async function generateFileDoc(filePath) {
try {
const relativePath = path.relative(config.sourceDir, filePath);
const outputPath = path.join(
config.outputDir,
relativePath.replace(/\.jsx?$/, '.md')
);
// Generate markdown from JSDoc
const markdown = await jsdoc2md.render({
files: filePath,
template: fs.readFileSync(config.templateFile, 'utf8'),
'no-cache': true,
configure: './jsdoc.json'
});
// Only create file if there's actual documentation
if (markdown && markdown.trim().length > 0) {
await fs.ensureDir(path.dirname(outputPath));
await fs.writeFile(outputPath, markdown);
console.log(`✓ Generated: ${outputPath}`);
return outputPath;
} else {
console.log(`⊘ Skipped (no docs): ${filePath}`);
return null;
}
} catch (error) {
console.error(`✗ Error processing ${filePath}:`, error.message);
return null;
}
}
/**
* Generate table of contents for API documentation
* @param {string[]} generatedFiles - List of generated markdown files
* @returns {Promise<void>}
*/
async function generateToc(generatedFiles) {
const tocEntries = generatedFiles
.filter(f => f !== null)
.map(filePath => {
const relativePath = path.relative(config.outputDir, filePath);
const name = path.basename(filePath, '.md');
const dir = path.dirname(relativePath);
return {
name: name,
href: relativePath.replace(/\\/g, '/'),
directory: dir
};
})
.sort((a, b) => {
// Sort by directory first, then by name
if (a.directory !== b.directory) {
return a.directory.localeCompare(b.directory);
}
return a.name.localeCompare(b.name);
});
// Group by directory
const grouped = {};
tocEntries.forEach(entry => {
const dir = entry.directory === '.' ? 'Root' : entry.directory;
if (!grouped[dir]) {
grouped[dir] = [];
}
grouped[dir].push(entry);
});
// Generate YAML
let yaml = '### YamlMime:TableOfContent\n';
yaml += 'items:\n';
Object.keys(grouped).sort().forEach(dir => {
yaml += `- name: ${dir}\n`;
yaml += ' items:\n';
grouped[dir].forEach(entry => {
yaml += ` - name: ${entry.name}\n`;
yaml += ` href: ${entry.href}\n`;
});
});
await fs.writeFile(path.join(config.outputDir, 'toc.yml'), yaml);
console.log(`✓ Generated: ${path.join(config.outputDir, 'toc.yml')}`);
}
/**
* Generate API index page
* @returns {Promise<void>}
*/
async function generateIndexPage() {
const indexContent = `# API Reference
This section contains auto-generated API documentation from JSDoc comments in the source code.
## Navigation
Use the navigation menu on the left to browse through the API documentation organized by module.
## Documentation Coverage
All public functions, classes, and modules are documented here. For conceptual guides and tutorials, see the [Articles](../docs/introduction.md) section.
## Contributing
When adding new code, please include JSDoc comments following our [documentation standards](../docs/documentation-standards.md).
`;
await fs.writeFile(path.join(config.outputDir, 'index.md'), indexContent);
console.log(`✓ Generated: ${path.join(config.outputDir, 'index.md')}`);
}
/**
* Main execution
*/
async function main() {
console.log('🚀 Starting documentation generation...\n');
// Clean output directory
await fs.emptyDir(config.outputDir);
console.log(`✓ Cleaned: ${config.outputDir}\n`);
// Find all source files
const pattern = path.join(config.sourceDir, '**/*.{js,jsx}');
const files = glob.sync(pattern, {
ignore: config.excludePatterns
});
console.log(`Found ${files.length} source files\n`);
// Generate documentation for each file
const generatedFiles = [];
for (const file of files) {
const result = await generateFileDoc(file);
if (result) {
generatedFiles.push(result);
}
}
console.log(`\n✓ Generated ${generatedFiles.length} documentation files\n`);
// Generate table of contents
await generateToc(generatedFiles);
// Generate index page
await generateIndexPage();
console.log('\n✅ Documentation generation complete!');
}
// Run
main().catch(error => {
console.error('❌ Documentation generation failed:', error);
process.exit(1);
});
3. Create JSDoc Template
scripts/api-template.hbs:
4. Create JSDoc Configuration
jsdoc.json:
{
"source": {
"includePattern": ".+\\.jsx?$",
"excludePattern": "(node_modules|docs|test|dist|build)"
},
"plugins": [
"plugins/markdown"
],
"opts": {
"encoding": "utf8",
"recurse": true
},
"tags": {
"allowUnknownTags": true,
"dictionaries": ["jsdoc", "closure"]
}
}
5. Update DocFX Configuration
docfx.json:
{
"build": {
"content": [
{
"files": ["api/**.md"],
"src": ".",
"dest": "api"
},
{
"files": ["docs/**.md", "*.md"]
}
],
"resource": [
{
"files": ["images/**"]
}
],
"dest": "_site",
"template": ["default", "modern"],
"globalMetadata": {
"_appTitle": "Your JavaScript Project",
"_appName": "Your Project",
"_enableSearch": true,
"_gitContribute": {
"repo": "https://github.com/your-org/your-repo",
"branch": "main"
}
}
}
}
Writing JSDoc Comments
Basic Function Documentation
/**
* Fetches user data from the API
* @param {string} userId - The unique user identifier
* @param {Object} options - Optional configuration
* @param {boolean} [options.includeProfile=false] - Include user profile
* @param {number} [options.timeout=5000] - Request timeout in ms
* @returns {Promise<User>} The user object
* @throws {NotFoundError} When user doesn't exist
* @throws {NetworkError} When network request fails
* @example
* const user = await fetchUser('123', { includeProfile: true });
* console.log(user.name);
*/
async function fetchUser(userId, options = {}) {
// Implementation
}
Class Documentation
/**
* Manages user authentication and sessions
* @class
* @example
* const auth = new AuthManager({ apiKey: 'xxx' });
* await auth.login('user@example.com', 'password');
*/
class AuthManager {
/**
* Creates an AuthManager instance
* @param {Object} config - Configuration object
* @param {string} config.apiKey - API key for authentication
* @param {string} [config.baseUrl] - Base URL for API
*/
constructor(config) {
this.config = config;
}
/**
* Authenticates a user
* @param {string} email - User email
* @param {string} password - User password
* @returns {Promise<AuthToken>} Authentication token
* @throws {AuthenticationError} When credentials are invalid
*/
async login(email, password) {
// Implementation
}
}
React Component Documentation
/**
* User profile card component
* @component
* @param {Object} props - Component props
* @param {User} props.user - User object to display
* @param {Function} [props.onEdit] - Callback when edit is clicked
* @param {boolean} [props.editable=false] - Whether card is editable
* @returns {React.Element} Rendered component
* @example
* <UserCard
* user={userData}
* editable={true}
* onEdit={(user) => console.log('Edit', user)}
* />
*/
function UserCard({ user, onEdit, editable = false }) {
return (
<div className="user-card">
<h2>{user.name}</h2>
{editable && <button onClick={() => onEdit(user)}>Edit</button>}
</div>
);
}
Module Documentation
/**
* User management utilities
* @module utils/userUtils
* @description Provides helper functions for user data manipulation
* @see {@link module:utils/authUtils} for authentication utilities
*/
/**
* Formats a user's full name
* @memberof module:utils/userUtils
* @param {User} user - User object
* @returns {string} Formatted full name
*/
export function formatUserName(user) {
return `${user.firstName} ${user.lastName}`;
}
Type Definitions
/**
* User object
* @typedef {Object} User
* @property {string} id - Unique identifier
* @property {string} email - Email address
* @property {string} firstName - First name
* @property {string} lastName - Last name
* @property {UserProfile} [profile] - Optional user profile
* @property {Date} createdAt - Account creation date
*/
/**
* User profile information
* @typedef {Object} UserProfile
* @property {string} bio - User biography
* @property {string} avatarUrl - Profile picture URL
* @property {string[]} interests - List of interests
*/
Per-File Documentation Generation
Why Per-File Generation?
Benefits:
- ✅ Better organization (mirrors source structure)
- ✅ Easier to navigate
- ✅ Clearer file-to-doc mapping
- ✅ Faster incremental builds
- ✅ Better for large codebases
Example Structure:
api/
├── index.md
├── toc.yml
├── services/
│ ├── AuthService.md
│ ├── UserService.md
│ └── DataService.md
├── components/
│ ├── UserCard.md
│ ├── Dashboard.md
│ └── Navigation.md
└── utils/
├── formatters.md
├── validators.md
└── helpers.md
Customizing Per-File Output
Modify scripts/generate-docs.js to customize output:
/**
* Custom markdown header for each file
*/
function generateFileHeader(filePath, metadata) {
const relativePath = path.relative(config.sourceDir, filePath);
return `---
uid: ${metadata.uid}
title: ${metadata.title}
source: ${relativePath}
---
# ${metadata.title}
**Source**: [\`${relativePath}\`](../../${relativePath})
`;
}
/**
* Add custom sections to generated docs
*/
function addCustomSections(markdown, filePath) {
// Add "Related Files" section
const relatedFiles = findRelatedFiles(filePath);
if (relatedFiles.length > 0) {
markdown += '\n## Related Files\n\n';
relatedFiles.forEach(file => {
markdown += `- [${file.name}](${file.path})\n`;
});
}
// Add "Used By" section
const usedBy = findUsages(filePath);
if (usedBy.length > 0) {
markdown += '\n## Used By\n\n';
usedBy.forEach(file => {
markdown += `- [${file.name}](${file.path})\n`;
});
}
return markdown;
}
Pipeline Integration
Azure DevOps Pipeline
azure-pipelines-docfx.yml (JavaScript version):
trigger:
branches:
include:
- main
- develop
paths:
include:
- src/**
- docs/**
- scripts/generate-docs.js
- package.json
- docfx.json
stages:
- stage: BuildDocumentation
displayName: 'Build Documentation'
jobs:
- job: Build
displayName: 'Generate and Build Docs'
pool:
vmImage: 'ubuntu-latest'
steps:
# Install Node.js
- task: NodeTool@0
inputs:
versionSpec: '18.x'
displayName: 'Install Node.js'
# Install dependencies
- script: npm ci
displayName: 'Install npm dependencies'
# Generate API documentation from JSDoc
- script: npm run docs:generate
displayName: 'Generate API docs from JSDoc'
# Install DocFX
- script: |
wget https://github.com/dotnet/docfx/releases/download/v2.70.0/docfx-linux-x64-v2.70.0.zip
unzip docfx-linux-x64-v2.70.0.zip -d docfx
chmod +x docfx/docfx
displayName: 'Install DocFX'
# Build documentation site
- script: ./docfx/docfx build docfx.json
displayName: 'Build documentation site'
# Publish artifact
- task: PublishPipelineArtifact@1
inputs:
targetPath: '_site'
artifact: 'documentation'
displayName: 'Publish documentation artifact'
# Elasticsearch indexing stage (same as C#)
- stage: IndexToElastic
displayName: 'Index to Elasticsearch'
dependsOn: BuildDocumentation
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- job: Index
displayName: 'Index Documentation'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: 'documentation'
path: '_site'
- task: PowerShell@2
inputs:
filePath: 'scripts/index-docs-to-elastic.ps1'
arguments: '-SitePath "_site" -RepositoryName "YourJavaScriptRepo"'
env:
ELASTIC_CLOUD_ID: $(ELASTIC_CLOUD_ID)
ELASTIC_API_KEY: $(ELASTIC_API_KEY)
displayName: 'Index to Elasticsearch'
# Central repository publishing (same as C#)
- stage: PublishToCentral
displayName: 'Publish to Central Repository'
dependsOn: IndexToElastic
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- job: PushToCentralRepo
displayName: 'Push to Central Docs Repo'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: 'documentation'
path: '_site'
- script: |
git clone $(CENTRAL_DOCS_REPO) central-docs
mkdir -p central-docs/repos/YourJavaScriptRepo
cp -r _site/* central-docs/repos/YourJavaScriptRepo/
cd central-docs
git config user.email "pipeline@example.com"
git config user.name "Azure Pipeline"
git add .
git commit -m "Update docs for YourJavaScriptRepo"
git push https://$(CENTRAL_DOCS_PAT)@dev.azure.com/...
displayName: 'Push to central repository'
Examples
Complete Example: Express.js API
src/routes/userRoutes.js:
/**
* User management routes
* @module routes/userRoutes
*/
const express = require('express');
const router = express.Router();
/**
* Get all users
* @route GET /api/users
* @param {express.Request} req - Express request object
* @param {express.Response} res - Express response object
* @returns {Promise<void>}
* @throws {DatabaseError} When database query fails
* @example
* // GET /api/users?limit=10&offset=0
* // Response: { users: [...], total: 100 }
*/
router.get('/', async (req, res) => {
try {
const { limit = 10, offset = 0 } = req.query;
const users = await User.findAll({ limit, offset });
res.json({ users, total: users.length });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
/**
* Get user by ID
* @route GET /api/users/:id
* @param {express.Request} req - Express request object
* @param {string} req.params.id - User ID
* @param {express.Response} res - Express response object
* @returns {Promise<void>}
* @throws {NotFoundError} When user doesn't exist
* @example
* // GET /api/users/123
* // Response: { id: '123', name: 'John Doe', ... }
*/
router.get('/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
module.exports = router;
Complete Example: React Component
src/components/UserDashboard.jsx:
import React, { useState, useEffect } from 'react';
/**
* User dashboard component
* @component
* @param {Object} props - Component props
* @param {string} props.userId - ID of user to display
* @param {Function} [props.onUserUpdate] - Callback when user is updated
* @param {Object} [props.theme] - Theme configuration
* @param {string} [props.theme.primaryColor='#007bff'] - Primary color
* @returns {React.Element} Dashboard component
* @example
* <UserDashboard
* userId="123"
* onUserUpdate={(user) => console.log('Updated:', user)}
* theme={{ primaryColor: '#ff0000' }}
* />
*/
export function UserDashboard({ userId, onUserUpdate, theme = {} }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
/**
* Fetches user data on mount and when userId changes
* @private
*/
useEffect(() => {
async function fetchUser() {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div className="user-dashboard" style={{ color: theme.primaryColor }}>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
Best Practices
1. Consistent Comment Style
Good:
/**
* Validates email format
* @param {string} email - Email to validate
* @returns {boolean} True if valid
*/
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
Bad:
// Validates email
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
2. Document Public APIs
- ✅ All exported functions
- ✅ All exported classes
- ✅ All React components
- ✅ All API routes
- ⊘ Private helper functions (optional)
3. Include Examples
/**
* Formats currency value
* @param {number} amount - Amount to format
* @param {string} [currency='USD'] - Currency code
* @returns {string} Formatted currency string
* @example
* formatCurrency(1234.56) // Returns: "$1,234.56"
* @example
* formatCurrency(1234.56, 'EUR') // Returns: "€1,234.56"
*/
function formatCurrency(amount, currency = 'USD') {
// Implementation
}
4. Link Related Documentation
/**
* Authenticates user
* @param {string} email - User email
* @param {string} password - User password
* @returns {Promise<AuthToken>} Authentication token
* @see {@link module:utils/tokenUtils} for token utilities
* @see {@link AuthManager#logout} for logout functionality
*/
async function login(email, password) {
// Implementation
}
5. Use Type Definitions
/**
* @typedef {Object} PaginationOptions
* @property {number} [page=1] - Page number
* @property {number} [limit=10] - Items per page
* @property {string} [sortBy='createdAt'] - Sort field
* @property {'asc'|'desc'} [sortOrder='desc'] - Sort direction
*/
/**
* Fetches paginated users
* @param {PaginationOptions} options - Pagination options
* @returns {Promise<PaginatedResult<User>>}
*/
async function getUsers(options) {
// Implementation
}
Integration Checklist
Phase 1: Setup (1 hour)
- Install jsdoc-to-markdown and dependencies
- Create
scripts/generate-docs.js - Create
scripts/api-template.hbs - Create
jsdoc.jsonconfiguration - Update
package.jsonwith doc scripts - Create
/docsdirectory structure - Update
docfx.jsonfor JavaScript
Phase 2: Documentation (2-3 hours)
- Add JSDoc comments to all public functions
- Add JSDoc comments to all classes
- Document all React components
- Document all API routes
- Create type definitions
- Write conceptual articles
Phase 3: Testing (30 minutes)
- Run
npm run docs:generate - Verify API markdown files created
- Run
docfx build docfx.json - Serve locally and test navigation
- Check cross-references work
Phase 4: Pipeline (1 hour)
- Update
azure-pipelines-docfx.yml - Add Node.js installation step
- Add npm install step
- Add docs generation step
- Test pipeline on feature branch
- Merge to main and verify full deployment
Troubleshooting
JSDoc Generation Issues
Problem: No markdown files generated
Solution: Check JSDoc comments exist and are properly formatted
Problem: Missing type information
Solution: Ensure @param and @returns tags include types
Problem: Template errors
Solution: Verify api-template.hbs syntax
DocFX Build Issues
Problem: Broken links in generated docs
Solution: Use relative paths in JSDoc @see tags
Problem: Missing navigation
Solution: Ensure toc.yml is generated correctly
Resources
Last Updated: 2025-11-28
Version: 1.0
Maintained By: Platform Team