“注释”生成“VitePress文档网站”只需要“一行命令”!来试试!


开门见山

先把工具掏出来。

jsdoc2vitepress

github

需求

你可能用过jsdoc,用代码里面的注释生成文档。但是苦于jsdoc生成的文档网页太不好看目录结构不好调整

你可能也用过VuePress或者VitePress。但是苦于手写Markdown文档太费时间

那有没有一种方案,可以直接用代码注释,生成vitepress构建的文档网页呢?

为什么不行?今天,作者封装了一个工具组件,满足你的需求!!

设计

我们大可不必从头撸起,只需要把jsdocVitePress巧妙结合一下。

实现思路

JavaScript注释->Markdown文档->VitePress站点

组件

这里面JavaScript注释->Markdown文档的部分,我们用到jsdoc2md这个组件来完成。

生成Markdown文件到指定目录后,我们希望不用修改VitePress的配置文件,就可以自动根据目录的结构,生成VitePress的侧边栏。这里我们用到作者的另一个组件vitepress-plugin-autobar。

封装

根据泰斯勒定律,也叫复杂性守恒定律,每一个过程都有其固有的复杂性,存在一个临界点,到达临界点之后就不能再简化了,你只能将固有的复杂性从一个地方转移到另外一个地方。

为了方便使用,我们把现有的组件封装一下,把上面想法的复杂性留给自己,给使用者留下最简单易用的jsdoc2vitepress组件。

实现

初始化VitePress文档目录

期望的效果是:

代码语言:javascript
复制
jsdoc2vitepress init

初始化出下面的VitePress文档目录

代码语言:javascript
复制
.
├─ docs
│  ├─ .vitepress
│  │  └─ config.js
│  │  └─ jsdoc2vitepress.config.js
│  └─ index.md

这里就是一个简单的模板脚手架的功能,可以参考我们之前的一篇文章来实现具体的代码。

Node.js脚手架开发完全指南「TypeScript版」

下面是具体的代码实现。

代码语言:javascript
复制
// init-docs.ts
import ora from 'ora';
import fs from 'fs-extra';
import gitclone from 'git-clone/promise';
import path from 'path';

/**

  • @module init-docs
  • @description init docs directory
    */

/**

  • @exports initDocs "jsdoc2vitepress init" init docs directory
    */
    export const initDocs = async () => {
    const loading = ora('Init docs directory');
    try {
    const templateGitUrl = 'https://github.com/luciozhang/jsdoc2vitepress-template.git';
    const docsDir = path.resolve(process.cwd(), 'docs');
    await gitclone(templateGitUrl, docsDir, { checkout: 'master', shallow: true });
    fs.removeSync(path.join(docsDir, '.git'));
    loading.succeed('Init docs directory success');
    } catch (error) {
    loading.fail(Init docs directory fail: ${error}\nPlease delete local 'docs' directory and retry.);
    }
    };

jsdoc生成Markdown

这一步主要是读取源码,用jsdoc2md对源码注释生成Markdown文档。

我们直接用这个组件的代码作为示例,代码的目录结构是下面这样的

预期效果是生成下面的Markdown文档。

Markdown内容是注释生成的API文档。

jsdoc2md需要一个配置文件jsdoc2md.config.json。这里上一步初始化的模板已经生成了一个可用的配置文件,更多的配置内容,参考Configuring JSDoc with a configuration file。

下面是具体代码实现。

代码语言:javascript
复制
// jsdoc-to-md.ts
import fs from 'fs-extra';
import path from 'path';
import jsdoc2md from 'jsdoc-to-markdown';
import ora from 'ora';

const configJ2VPath = path.resolve(process.cwd(), 'docs', '.vitepress', 'jsdoc2vitepress.config.json');
const configJ2MPath = path.resolve(process.cwd(), 'docs', '.vitepress', 'jsdoc2md.config.json');

/**

  • @module jsdoc-to-md
  • @description Generates Markdown API documentation from jsdoc annotated source code.
    */

/**

  • @exports jsdocToMd Generates Markdown

  • @returns {Promise} Return promise to check if generate success
    */
    export const jsdocToMd = async () => {
    const loading = ora('Generates Markdown');
    try {
    const { markdownDirs } = await fs.readJSON(configJ2VPath);

    await Promise.all(markdownDirs.map(async (sourceObject: any) => {
    const { root, output, ingoreList } = sourceObject;
    await makeMarkDownForFiles(root, output, ingoreList);
    }));
    loading.succeed('Generates Markdown success');
    } catch (error) {
    loading.fail(Generates Markdown fail${error});
    }
    };

const makeMarkDownForFiles = async (root: string, output: string, ingoreList: Array<string>) => {
const fileList = await fs.readdir(root);
await Promise.all(fileList.map(async (fileName) => {
if (ingoreList.indexOf(fileName) === -1) {
await makeMarkDownDoc(fileName, root, output);
}
}));
};

const makeMarkDownDoc = async (sourceName: string, sourceRootPath: string, outputPath: string) => {
let sourcePath = ${sourceRootPath}/${sourceName};
const outputName = sourceName.replace('.js', '').replace('.ts', '');

const loading = ora(Generates Markdown for ${sourcePath});
try {
// 处理js文件的路径,需要区分是文件或目录,目录会将目录下所有文件生成为一个md
const stat = fs.lstatSync(sourcePath);
if (stat.isDirectory()) {
sourcePath = ${sourcePath}/*;
}

const mdStr = await jsdoc2md.render({
  &#39;example-lang&#39;: &#39;javascript&#39;,
  files: path.resolve(process.cwd(), sourcePath),
  &#39;name-format&#39;: &#39;backticks&#39;,
  &#39;heading-depth&#39;: 2,
  &#39;module-index-format&#39;: &#39;none&#39;,
  configure: path.resolve(process.cwd(), configJ2MPath),
});
if (mdStr) {
  fs.outputFile(path.resolve(process.cwd(), `${outputPath}/${outputName}.md`), mdStr);
  loading.succeed(&#39;Generates Markdown success in &#39; + `${outputPath}/${outputName}.md`);
}

} catch (error) {
loading.fail(Generates Markdown fail for ${sourcePath});
}
};

Markdown生成Vitepress站点

有了Markdown文档,我们接下来就是简单的运行Vitepress框架,生成Vitepress文档站点了。

这里我们额外加一个优化,自动生成Vitepress的侧边栏配置,用到作者的另一个组件vitepress-plugin-autobar。

这一步主要是用shell执行vitepress命令,具体代码如下。

代码语言:javascript
复制
//md-to-vitepress.ts
import shell from 'shelljs';
/**

  • @module md-to-vitepress
  • @description Generates VitePress API documentation from jsdoc annotated source code.
    */

/**

  • @exports startVitePress Run VitePress server
  • @returns {Promise} Return promise to check if run success
    */
    export const startVitePress = async () => {
    shell.exec('node_modules/.bin//vitepress dev docs');
    };

/**

  • @exports buildVitePress build VitePress server
  • @returns {Promise} Return promise to check if build success
    */
    export const buildVitePress = async () => {
    shell.exec('node_modules/.bin/vitepress build docs');
    };

整合命令行工具

最后,就只需要在入口文件,把上面封装的方法,整合成命令行工具了,具体代码如下

这里用到了commander库。

代码语言:javascript
复制
// index.ts
#!/usr/bin/env node

import { Command } from 'commander';
import { initDocs } from './init-docs';
import { jsdocToMd } from './jsdoc-to-md';
import { startVitePress, buildVitePress } from './md-to-vitepress';

const program = new Command();

program
.name('jsdoc2vitepress')
.description('Generates vitepress API documentation from jsdoc annotated source code.')
.version('1.0.0');

program
.command('init')
.description('init vitepress directory.')
.action(async () => {
await initDocs();
});

program
.command('start')
.description('Generates vitepress API documentation from jsdoc annotated source code and run Vitepress')
.action(async () => {
await jsdocToMd();
await startVitePress();
});

program
.command('build')
.description('Generates vitepress API documentation from jsdoc annotated source code and run Vitepress')
.action(async () => {
await jsdocToMd();
await buildVitePress();
});

program.parse();

最终效果

初始化文档,运行文档网站

代码语言:javascript
复制
jsdoc2vitepress init
jsdoc2vitepress start

控制台输出

文档网站

构造VitePress网站

代码语言:javascript
复制
jsdoc2vitepress build

控制台输出

构建结果

使用建议

这个工具最适合用于给组件库生成文档,建议配合CI/CD,在提交组件库代码的时候,触发构建文档和发布npm的流程,由代码注释生成文档并构建发布,从而确保组合库和文档的一致性。

来试试吧!

jsdoc2vitepress

github