文件目录
使用npm安装jsdoc后,其文件目录结构如下:
其中templates放置了默认的主题模板。其中default主题的文件结构如下:
其中,publish.js是最重要的文件,它导出一个
publish
函数,在cli.js
中被调用
// cli.js
publishPromise = template.publish(
// 保存所有数据的Taffy对象,见下面
taffy(props.docs),
// env是一个保存运行环境信息的对象
// 参考:lib/jsdoc/env.js
env.opts,
// 暂时未
resolver.root
);
// publish.js
// 这里的publish就是上面的template.publish
exports.publish = function (taffyData, opts, tutorials) {
// ...
}
参考:taffydb
而static文件夹放置静态文件,它会被原封不动地复制到生成的文件夹中,tmpl是模板文件
publish.js解析
publish函数参数
function (taffyData, opts, tutorials)
taffyData是包含所有doclet的taffyData,doclet是@link,@name...
比如使用了@name,就可以使用docs[0].name获取第一个@name值
模板
和模板相关的模块是:
const template = require('jsdoc/template'); // 模板定义
const helper = require('jsdoc/util/templateHelper'); // 模板的辅助函数库
生成template的语句是
// templatePath是配置文件中的opts.template或者使用cli时的template选项的值
// 它应该是模板文件夹`tmpl`的所在文件夹的路径`(主题文件夹的根目录)`
view = new template.Template( path.join(templatePath, 'tmpl') );
再看template.js导出类的构造函数
constructor(filepath) {
// 模板文件所在路径
this.path = filepath;
// layout的文件名
this.layout = null;
// 表明使用的模板语法
this.settings = {
evaluate: /<\?js([\s\S]+?)\?>/g,
interpolate: /<\?js=([\s\S]+?)\?>/g,
escape: /<\?js~([\s\S]+?)\?>/g
};
}
layout的初始化语句:
// env.conf是配置文件导出的对象
conf = env.conf.templates || {};
conf.default = conf.default || {};
// ....
// 设置layout的路径
view.layout = conf.default.layoutFile ?
path.getResourcePath(path.dirname(conf.default.layoutFile),
path.basename(conf.default.layoutFile) ) :
'layout.tmpl';
所以,如果要自定义模板(假设为selfTheme,以下都以其为例)的配置项,只需要使用env.conf.templates
获取即可。例如:
// conf.json
{
templates: {
selfProp: 'selfValue'
}
}
// publish.js
conf = env.conf.templates || {};
conf.selfProp = conf.selfProp; // get selfProp
template.js导出类的方法如下:
- load(file: string) // 解析模板
- partial(file: string, data: any) // 引用另一模板文件,data是载入模板的数据
- render(file: string, data: any) // 渲染模板
重点看render代码:
render(file, data) {
// 渲染当前模板文件
let content = this.partial(file, data);
if (this.layout) {
// 将当前内容赋值给data.content,传递给layout模板
data.content = content;
content = this.partial(this.layout, data);
}
return content;
}
因此,在layout.tmpl文件中,有content变量表示当前页内容
<?js= content ?>
当然,同时具有data包含的其它属性变量
在publish.js中,直接使用render函数的有两处。
// function generate(title, docs, filename, resolveLinks)
docData = {
env: env,
title: title, // 标题
docs: docs // 见下面docs
};
html = view.render('container.tmpl', docData);
// function generateTutorial(title, tutorial, filename)
const tutorialData = {
title: title,
header: tutorial.title,
content: tutorial.parse(),
children: tutorial.children
};
const tutorialPath = path.join(outdir, filename);
let html = view.render('tutorial.tmpl', tutorialData);
所以,要修改各页面的标题,只需要将调用这两个函数的title函数换掉,比如:
generate('Global', [{kind: 'globalobj'}], globalUrl)
// 改成
generate('MyGlobal', [{kind: 'globalobj'}], globalUrl)
不过重点还是添加模板变量和方法
// 添加变量,只需要改变render调用时的data,比如以下改变container.tmpl的变量
docData = {
env: env,
title: title, // 标题
docs: docs,
myVar: 'myVar'
};
html = view.render('container.tmpl', docData);
// 添加方法
view.find = find;
view.linkto = linkto;
view.resolveAuthorLinks = resolveAuthorLinks;
view.tutoriallink = tutoriallink;
view.htmlsafe = htmlsafe;
view.outputSourceFiles = outputSourceFiles;
view.nav = buildNav(members);
// 在模板中可以使用`this.method`来调用,因为模板的this指向view
// 比如
// <?js this.find() ?>
添加到view中的方法/属性:
- find(spec)
通过find({kind: 'class'})来获取所有class,也可以添加其它doclet:find({kind, 'class', memberOf: 'Class1' }) - linkTo(longname, linkText)
渲染一个跳转到longname即namepath所指代的url的链接(a标签),文本是linkText
resolveAuthorLinks
渲染形如Jane Doe <jdoe@example.org>
的文本为<a href="mailto:jdoe@example.org">Jane Doe</a>
- htmlsafe(str) 转义html文本
- outputSourceFiles 是否导出源文件的标志
- nav 导航栏标签
docs
docs是一个包含数据的Taffy对象,它的来源是:
data = taffyData
// 获取各部分的数据taffy对象
members = helper.getMembers(data)
classes = taffy(members.classes);
modules = taffy(members.modules);
namespaces = taffy(members.namespaces);
mixins = taffy(members.mixins);
externals = taffy(members.externals);
interfaces = taffy(members.interfaces);
// 获取对应longname的docs
const myClasses = helper.find(classes, {longname: longname});
const myExternals = helper.find(externals, {longname: longname});
const myInterfaces = helper.find(interfaces, {longname: longname});
const myMixins = helper.find(mixins, {longname: longname});
const myModules = helper.find(modules, {longname: longname});
const myNamespaces = helper.find(namespaces, {longname: longname});
// myClasses等就是传入模板文件的docs变量
if (myModules.length) {
generate(`Module: ${myModules[0].name}`, myModules, helper.longnameToUrl[longname]);
}
if (myClasses.length) {
generate(`Class: ${myClasses[0].name}`, myClasses, helper.longnameToUrl[longname]);
}
if (myNamespaces.length) {
generate(`Namespace: ${myNamespaces[0].name}`, myNamespaces, helper.longnameToUrl[longname]);
}
if (myMixins.length) {
generate(`Mixin: ${myMixins[0].name}`, myMixins, helper.longnameToUrl[longname]);
}
if (myExternals.length) {
generate(`External: ${myExternals[0].name}`, myExternals, helper.longnameToUrl[longname]);
}
if (myInterfaces.length) {
generate(`Interface: ${myInterfaces[0].name}`, myInterfaces, helper.longnameToUrl[longname]);
}
在模板中则用this.find({kind: 'class'})代替(view.find)
docs的元素内容是:
// 参考doclet.js
{
comment: ''
name: '',,
kind: '',
// ...
}
特别的:
// source
{
kind: 'source',
code: ''
}
// main page
{
kind: 'mainpage',
readme: opts.readme,
longname: (opts.mainpagetitle) ? opts.mainpagetitle : 'Main Page'
}
// global
{
kind: 'globalobj'
}
tutorials相关待续