API documentation generator for JavaScript that parses source code and JSDoc comments to produce HTML documentation
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
JSDoc's template system generates HTML documentation from parsed doclets using a flexible template engine. It supports custom templates, layouts, helper functions, and complete control over output formatting.
Core template class for loading and rendering templates with data.
class Template {
/**
* Create a new Template instance
* @param filepath - Path to templates directory
*/
constructor(filepath: string);
/**
* Load template from file and compile
* @param file - Template filename
* @returns Compiled template function
*/
load(file: string): Function;
/**
* Render partial template with data
* @param file - Template filename
* @param data - Template data object
* @returns Rendered HTML string
*/
partial(file: string, data: object): string;
/**
* Render complete template with layout
* @param file - Template filename
* @param data - Template data object
* @returns Rendered HTML string with layout applied
*/
render(file: string, data: object): string;
/**
* Template settings for custom tag syntax
*/
settings: {
evaluate: RegExp; // Code execution tags: <?js ... ?>
interpolate: RegExp; // Variable interpolation: <?js= ... ?>
escape: RegExp; // HTML escaping: <?js~ ... ?>
};
/**
* Layout template path (optional)
*/
layout: string | null;
/**
* Template cache for performance
*/
cache: { [file: string]: Function };
}Standard interface for template entry points.
/**
* Main template publish function interface
* @param data - TaffyDB collection of all doclets
* @param opts - JSDoc options and configuration
* @param tutorials - Tutorial root object (optional)
* @returns Promise or undefined
*/
function publish(
data: TaffyDB,
opts: JSDocOptions,
tutorials?: TutorialRoot
): Promise<void> | void;
interface JSDocOptions {
destination: string; // Output directory
encoding: string; // File encoding
template: string; // Template path
readme?: string; // README content
package?: object; // Package information
access?: string[]; // Access levels to include
private?: boolean; // Include private members
query?: object; // Custom query parameters
}
interface TutorialRoot {
title: string;
children: Tutorial[];
}The standard HTML template included with JSDoc.
Location: templates/default/
Key Files:
publish.js - Main template entry pointtmpl/ - Template fragmentsstatic/ - CSS, JavaScript, and other assetsFeatures:
Minimalist template with clean design.
Location: templates/haruki/
Features:
Template that generates no output (for testing/validation).
Location: templates/silent/
Usage: Useful for syntax checking without generating files.
my-template/
├── publish.js # Main entry point
├── tmpl/ # Template fragments
│ ├── layout.tmpl # Main layout
│ ├── container.tmpl # Content container
│ └── ...
├── static/ # Static assets
│ ├── styles/ # CSS files
│ ├── scripts/ # JavaScript files
│ └── fonts/ # Font files
└── README.md # Template documentation// publish.js
const doop = require('jsdoc/util/doop');
const env = require('jsdoc/env');
const fs = require('jsdoc/fs');
const helper = require('jsdoc/util/templateHelper');
const path = require('jsdoc/path');
const { taffy } = require('@jsdoc/salty');
const template = require('jsdoc/template');
let data;
let view;
let outdir = path.normalize(env.opts.destination);
function find(spec) {
return helper.find(data, spec);
}
exports.publish = function(taffyData, opts, tutorials) {
data = taffyData;
const conf = env.conf.templates || {};
conf.default = conf.default || {};
const templatePath = path.normalize(env.opts.template);
view = new template.Template(path.join(templatePath, 'tmpl'));
// Generate output files
generateFiles();
};
function generateFiles() {
const classes = find({ kind: 'class' });
const namespaces = find({ kind: 'namespace' });
classes.forEach(function(c) {
generateClassDoc(c);
});
namespaces.forEach(function(n) {
generateNamespaceDoc(n);
});
}// publish.js with helpers
const helper = require('jsdoc/util/templateHelper');
// Template helper functions
const htmlsafe = helper.htmlsafe;
const linkto = helper.linkto;
const resolveAuthorLinks = helper.resolveAuthorLinks;
function buildNav(members) {
let nav = '<ul>';
if (members.classes.length) {
nav += '<li>Classes<ul>';
members.classes.forEach(function(c) {
nav += `<li>${linkto(c.longname, c.name)}</li>`;
});
nav += '</ul></li>';
}
if (members.namespaces.length) {
nav += '<li>Namespaces<ul>';
members.namespaces.forEach(function(n) {
nav += `<li>${linkto(n.longname, n.name)}</li>`;
});
nav += '</ul></li>';
}
nav += '</ul>';
return nav;
}
function generateSourceFiles() {
const sourceFiles = {};
data().each(function(doclet) {
if (doclet.meta) {
const sourcePath = getPathFromDoclet(doclet);
sourceFiles[sourcePath] = {
shortened: sourcePath,
resolved: sourcePath
};
}
});
return sourceFiles;
}
exports.publish = function(taffyData, opts, tutorials) {
data = taffyData;
const members = helper.getMembers(data);
const sourceFiles = generateSourceFiles();
const nav = buildNav(members);
// Generate index page
const indexHtml = view.render('index.tmpl', {
title: opts.package?.name || 'API Documentation',
nav: nav,
members: members
});
fs.writeFileSync(path.join(outdir, 'index.html'), indexHtml, 'utf8');
};tmpl/layout.tmpl)<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?js= title ?></title>
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title"><?js= title ?></h1>
<?js= content ?>
</div>
<nav>
<?js= this.nav ?>
</nav>
<script src="scripts/prettify/prettify.js"></script>
<script>prettyPrint();</script>
</body>
</html>tmpl/class.tmpl)<?js
var self = this;
var isGlobalPage;
docs.forEach(function(doc, i) {
?>
<section>
<header>
<h2><?js if (doc.ancestors && doc.ancestors.length) { ?>
<span class="ancestors"><?js= doc.ancestors.join('') ?></span>
<?js } ?>
<?js= doc.name ?>
<?js if (doc.variation) { ?>
<sup class="variation"><?js= doc.variation ?></sup>
<?js } ?></h2>
<?js if (doc.classdesc) { ?>
<div class="class-description"><?js= doc.classdesc ?></div>
<?js } ?>
</header>
<article>
<div class="container-overview">
<?js if (doc.kind === 'class' && doc.examples && doc.examples.length) { ?>
<h3>Example<?js= doc.examples.length > 1? 's':'' ?></h3>
<?js= this.partial('examples.tmpl', doc.examples) ?>
<?js } ?>
</div>
<?js if (doc.augments && doc.augments.length) { ?>
<h3 class="subsection-title">Extends</h3>
<?js= this.partial('augments.tmpl', doc.augments) ?>
<?js } ?>
<?js if (doc.requires && doc.requires.length) { ?>
<h3 class="subsection-title">Requires</h3>
<ul><?js doc.requires.forEach(function(r) { ?>
<li><?js= self.linkto(r, r) ?></li>
<?js }); ?></ul>
<?js } ?>
</article>
</section>
<?js }); ?>JSDoc provides built-in helper functions for common template tasks.
const helper = require('jsdoc/util/templateHelper');
/**
* Create HTML-safe string
* @param str - String to make HTML-safe
* @returns HTML-escaped string
*/
function htmlsafe(str: string): string;
/**
* Create link to symbol
* @param longname - Symbol longname
* @param linkText - Link text (optional)
* @returns HTML link element
*/
function linkto(longname: string, linkText?: string): string;
/**
* Find doclets matching specification
* @param data - TaffyDB data collection
* @param spec - Search specification
* @returns Array of matching doclets
*/
function find(data: TaffyDB, spec: object): Doclet[];
/**
* Get organized members from data
* @param data - TaffyDB data collection
* @returns Organized member object
*/
function getMembers(data: TaffyDB): {
classes: Doclet[];
externals: Doclet[];
events: Doclet[];
globals: Doclet[];
mixins: Doclet[];
modules: Doclet[];
namespaces: Doclet[];
tutorials: Doclet[];
interfaces: Doclet[];
};
/**
* Resolve author links in text
* @param str - String with author information
* @returns String with resolved links
*/
function resolveAuthorLinks(str: string): string;
/**
* Get ancestor links for symbol
* @param data - TaffyDB data collection
* @param doclet - Current doclet
* @returns Array of ancestor link strings
*/
function getAncestorLinks(data: TaffyDB, doclet: Doclet): string[];// Custom template helpers
function formatDate(date) {
return new Date(date).toLocaleDateString();
}
function getTypeNames(type) {
if (!type || !type.names) return 'unknown';
return type.names.join(' | ');
}
function buildParameterTable(params) {
if (!params || !params.length) return '';
let table = '<table class="params"><thead><tr><th>Name</th><th>Type</th><th>Description</th></tr></thead><tbody>';
params.forEach(function(param) {
table += '<tr>';
table += `<td>${param.name}</td>`;
table += `<td>${getTypeNames(param.type)}</td>`;
table += `<td>${param.description || ''}</td>`;
table += '</tr>';
});
table += '</tbody></table>';
return table;
}
// Use in templates
exports.publish = function(taffyData, opts, tutorials) {
// Make helpers available to templates
view.formatDate = formatDate;
view.getTypeNames = getTypeNames;
view.buildParameterTable = buildParameterTable;
};# Use specific template
jsdoc -t templates/custom src/
# Use template from node_modules
jsdoc -t node_modules/my-jsdoc-template src/{
"opts": {
"template": "./templates/custom"
},
"templates": {
"cleverLinks": false,
"monospaceLinks": false,
"dateFormat": "MMMM Do YYYY, h:mm:ss a",
"outputSourceFiles": true,
"outputSourcePath": true,
"systemName": "My API",
"footer": "Copyright 2023",
"copyright": "Copyright 2023 My Company",
"includeDate": true,
"navType": "vertical",
"theme": "cerulean",
"linenums": true,
"collapseSymbols": false,
"inverseNav": true,
"protocol": "html://",
"methodHeadingReturns": false
}
}// simple-template/publish.js
const fs = require('jsdoc/fs');
const path = require('jsdoc/path');
const { taffy } = require('@jsdoc/salty');
exports.publish = function(data, opts) {
const outdir = path.normalize(opts.destination);
// Generate simple HTML
let html = `
<!DOCTYPE html>
<html>
<head><title>API Documentation</title></head>
<body>
<h1>API Documentation</h1>
<ul>
`;
data({ kind: ['class', 'function', 'namespace'] }).each(function(doclet) {
html += `<li><strong>${doclet.name}</strong> - ${doclet.description || 'No description'}</li>`;
});
html += `
</ul>
</body>
</html>
`;
fs.writeFileSync(path.join(outdir, 'index.html'), html, 'utf8');
};// json-template/publish.js
const fs = require('jsdoc/fs');
const path = require('jsdoc/path');
exports.publish = function(data, opts) {
const outdir = path.normalize(opts.destination);
const docs = data().get().map(function(doclet) {
return {
name: doclet.name,
longname: doclet.longname,
kind: doclet.kind,
description: doclet.description,
params: doclet.params,
returns: doclet.returns,
examples: doclet.examples
};
});
const output = {
generated: new Date().toISOString(),
package: opts.package,
docs: docs
};
fs.writeFileSync(
path.join(outdir, 'api.json'),
JSON.stringify(output, null, 2),
'utf8'
);
};// markdown-template/publish.js
const fs = require('jsdoc/fs');
const path = require('jsdoc/path');
exports.publish = function(data, opts) {
const outdir = path.normalize(opts.destination);
let markdown = `# ${opts.package?.name || 'API'} Documentation\n\n`;
const classes = data({ kind: 'class' }).get();
const functions = data({ kind: 'function' }).get();
if (classes.length) {
markdown += '## Classes\n\n';
classes.forEach(function(cls) {
markdown += `### ${cls.name}\n\n`;
if (cls.description) {
markdown += `${cls.description}\n\n`;
}
});
}
if (functions.length) {
markdown += '## Functions\n\n';
functions.forEach(function(fn) {
markdown += `### ${fn.name}\n\n`;
if (fn.description) {
markdown += `${fn.description}\n\n`;
}
if (fn.params && fn.params.length) {
markdown += '**Parameters:**\n\n';
fn.params.forEach(function(param) {
markdown += `- \`${param.name}\` - ${param.description || ''}\n`;
});
markdown += '\n';
}
});
}
fs.writeFileSync(path.join(outdir, 'README.md'), markdown, 'utf8');
};