Skip to main content

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

  1. Overview
  2. JSDoc vs XML Comments
  3. Setup and Configuration
  4. Writing JSDoc Comments
  5. Per-File Documentation Generation
  6. Pipeline Integration
  7. Examples
  8. 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

AspectC# (DocFX)JavaScript (JSDoc)
Comment StyleXML (///)JSDoc (/** */)
Extraction ToolDocFX metadatajsdoc-to-markdown
Output FormatYAMLMarkdown
Per-File DocsNoYes (recommended)
Build ProcessSingle commandMulti-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:

---
uid: {{name}}
---

# {{name}}

{{#if description}}
{{{description}}}
{{/if}}

{{#if examples}}
## Examples

{{#each examples}}
{{{this}}}
{{/each}}
{{/if}}

{{#if params}}
## Parameters

{{#each params}}
### {{name}}

**Type**: `{{{type.names}}}`{{#if optional}} (optional){{/if}}

{{{description}}}

{{#if defaultvalue}}
**Default**: `{{{defaultvalue}}}`
{{/if}}

{{/each}}
{{/if}}

{{#if returns}}
## Returns

**Type**: `{{{returns.0.type.names}}}`

{{{returns.0.description}}}
{{/if}}

{{#if throws}}
## Throws

{{#each throws}}
- **{{{type.names}}}**: {{{description}}}
{{/each}}
{{/if}}

{{#if see}}
## See Also

{{#each see}}
- {{{this}}}
{{/each}}
{{/if}}

---

*Generated from source code*

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
}
/**
* 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.json configuration
  • Update package.json with doc scripts
  • Create /docs directory structure
  • Update docfx.json for 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

Build StatusBuild #20251224.44 | Commit: 2544997 | Branch: HEAD | Built: 12/24/2025, 4:40:09 PM