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

2,047 阅读4分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

开门见山

先把工具掏出来。

jsdoc2vitepress

github

需求

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

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

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

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

设计

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

实现思路

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

总体流程

组件

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

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

封装

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

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

实现

初始化VitePress文档目录

期望的效果是:

jsdoc2vitepress init

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

.
├─ docs
│  ├─ .vitepress
│  │  └─ config.js
│  │  └─ jsdoc2vitepress.config.js
│  └─ index.md

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

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

下面是具体的代码实现。

// 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文档。

jstomd-process

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

源码目录

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

md目录

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

MD内容

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

下面是具体代码实现。

// 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({
      'example-lang': 'javascript',
      files: path.resolve(process.cwd(), sourcePath),
      'name-format': 'backticks',
      'heading-depth': 2,
      'module-index-format': 'none',
      configure: path.resolve(process.cwd(), configJ2MPath),
    });
    if (mdStr) {
      fs.outputFile(path.resolve(process.cwd(), `${outputPath}/${outputName}.md`), mdStr);
      loading.succeed('Generates Markdown success in ' + `${outputPath}/${outputName}.md`);
    }
  } catch (error) {
    loading.fail(`Generates Markdown fail for ${sourcePath}`);
  }
};

Markdown生成Vitepress站点

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

mdtovitepress-process

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

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

//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库。

// 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();

最终效果

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

jsdoc2vitepress init
jsdoc2vitepress start

控制台输出

init&start

文档网站

文档网站

构造VitePress网站

jsdoc2vitepress build

控制台输出

构建网站

构建结果

vitepress-dist

使用建议

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

来试试吧!

jsdoc2vitepress

github