手把手教你设计npm包-系列三(文档页面搭建)

345 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

——Bug修复员-鲁宽宽

前言


系列二讲述了如何从零到一搭建简单实用的npm包,也是核心的实现。

 接下来的两篇分别是【文档页面】与【验证页面】的搭建。

 此篇主要讲如何搭建一款可配置、灵活的使用说明文档。

 页面由VuePress来生成,它是以Markdown 为中心的项目结构,通过最少的配置来实现静态网站的构建。

 由于以往的文档是由jsdocs通过分析注释一键完成,而VuePress是以md文件构成,故面临的困难点就是如何将注释生成md文件。

实现流程


 思路为以下几个过程:

  • 安装并配置VuePress
  • 解析js注释
  • 写入md文件
  • 执行vuepress构建
  • 部署服务器
安装配置

 若是没有使用过VuePress,可以查看官方网站,上手很简单的。

 在项目本地安装

npm install vuepress -D

此篇只讲vuepress的使用,建议参考源码,下图为整体目录结构。

图一

// docs/.vuepress/config.js 配置
module.exports = {
  base: '/web/js-utils/', // 部署路径
  title: 'JS-utils',
  description: 'js、utils', // 以meta标签显示
  head: [
    ['link', { rel: 'icon', href: '/favicon.ico' }]
  ],
  themeConfig: {
    logo: 'https://pic5.zhuanstatic.com/zhuanzh/62a63713-6579-4f7e-9d24-b7e80a21b1cf.png',
    nav: [
      { text: '主页', link: '/' },
      { text: 'API', link: '/main/' }, // 对应main文件夹下的md文件
      { text: '更新记录', link: '/updateLog/' },
      { text: '博主首页', link: 'https://www.jintingyo.com', target:'_blank' },
    ],
    sidebar: 'auto',
    smoothScroll: true
  }
}

// package.json  新增
"scripts": {
    "docs": "node build/docs/index.js",  // 解析注释并写入md文件
    "docs:dev": "vuepress dev docs", // 本地预览
    "docs:build": "vuepress build docs", // 打包
},

 上述配置完成后就可以执行VuePress命令进行构建,支持本地预览与生产环境打包。

 执行npm run docs:dev,可以进行本地预览,说明配置没有问题,真香(vuepress的强大不止这些,可以自行摸索)。

 接下来要做的就是如何将注释写入main/index.md文件。

解析js注释

  主要是通过正则等匹配方式来分析注释,找到规则并输出。

在此之前先声明下: 由于使用的不是jsdoc,故不需要遵守其api属性,可以设计自己的命名规范。

  如注释内容:

/**
 * @name regIdCardCode
 * @type 正则
 * @description 校验中国身份证号(包含x)
 * @version 0.0.22
 * @param {String} code 必填项,身份证号
 * @todo 
 * @returns {Boolean} true 或者 false
 * @parameter 41072219940212543x
 * @example
 *  import { regIdCardCode } from "@lu-kk/js-utils"
 *  const code = "41072219940212543x";
 *  regIdCardCode(code)  // true or false
 */

  相关字段说明:

@name : 方法名  
@type :用于归类 正则
@description : 描述 
@version :  版本
@param :入参  
@todo :  额外记录  
@returns : 返回值与callback互斥  
@callback :回调函数  
@parameter :便捷用例的参数 
@example :例子  

 这样不需要依赖第三方包,可以做到快速响应、定制化。

 解析注释的代码实现:

  • node读取所有文件下的注释
// 所有文件的内的内容
let allFilesContent = ''
allFiles.forEach(item => {
  const fileContent = fs.readFileSync(item, 'utf-8');
  allFilesContent += fileContent
})
  • 分析注释组合为对象

 下面只是部分代码,主要就是通过正则等将字符串转为了数组对象。

/**
 * 处理注释文件为数组返回;
 * @param {String} originContent - 源文件
 */
const analysisNotes = (originContent) => {
  let contentList = originContent.match(/\/\*\*[\s\S]+?\*\//g);
  // console.log('contentList----', contentList)
  let result = [];
  contentList.forEach((item) => {
    // 消除换行及/** */
    let content = item.replace(/[\r\n]|\/\*\*|\*\//g, '');
    // console.log('content------', content)
    let lineData = content.split('* @');
    lineData.shift()
    // console.log('lineData---', lineData)
    result.push(lineData)
  })
  return result;
};
/**
 * 处理注释文件为数组返回;  
 * @param {String} originContent - 注释内容
 */
exports.formatContent = (originContent) => {
  let notesList = analysisNotes(originContent);
  // console.log('notesList---', notesList)
  let result = [];
  notesList.forEach((notes) => {
    let notesMap = {}
    let isDocTrue = false;
    notes.forEach(note => {
      try {
        let noteLsit = note.split(' ')
        let key = noteLsit[0]
        let value = noteLsit.slice(1).join(' ')
        if (key == 'example') {
          let exampleContent = value.replace(/\*/g, "").trim();
          value = beautify(exampleContent, {
            indent_size: 2
          });
        }
        // 处理param
        if (key == 'param') {
          let params = {
            type: value.split(' ')[0].trim().replace(/\}|\{/g, ""),
            key: value.split(' ')[1].trim(),
            value: value.split(' ').slice(2).join(''),
          }
          // console.log('params-------', params)
          if (Array.isArray(notesMap[key])) {
            notesMap[key].push(params)
          } else {
            notesMap[key] = []
            notesMap[key].push(params)
          }
        } else {
          notesMap[key] = value
        }
      } catch {
        isDocTrue = true;
        console.log(`--------------------- \n#### error ####:请检查注释是否规范!\n#### data  ####:${notes}; \n#### line  ####:${note}; \n---------------------`)
      }
    })
    if (!isDocTrue) {
      result.push(notesMap)
    }
  })
  return sortHandle(result, 'name') || [];
};

// 最终效果
let result = [{
    name: '',
    type: ''
    ...
}]
写入md文件

 通过上一步获取到了一个数组对象,这一环节就是将数组写入md文件。也是比较简单的,其流程为:

  • 设计模板
  • 循环数组生成
  • 写入文件

 如下面我想要的效果图:

图二

 具体代码实现:

/**
 * 写入markdown文件
 * @param {String} writePath - 写入文件的路径
 */
exports.writeDocs = (writePath, contentList) => {
  const dataSource = formatHandle(contentList)
  // console.log(writePath, dataSource)
let mdText = 
`# API集合`;
  // 动态渲染每一个方法
dataSource.forEach(item => {
let content = 
`
***
## ${item.type}
`;
item.data.forEach(data => {
content += `
***
### ${data.name}
`
content +=
`
* **功能**:
  * ${data.description}
* **版本**:
  * ${data.version}
* **参数**:
`
let table = `
| 参数 | 备注 | 类型 |  
|------|------|------|------|  
`;
data.param.forEach(item => {
  table += `|${item.key || ''}|${item.value || ''}|${item.type}|
`;
content += table;
})
if (data.returns) {
content += `
* **返回值**:
  * ${data.returns}
`;
}
let example = `
* **示例**:
`;
example += '\n```javascript\n'+ data.example + '\n```\n';
content += example;
let url = `https://jintingyo.com/h5/js-utils-test-page?sdkId=${data.name}`
let qrCode = `
* **扫描二维码测试**:  
<img width="200px" height="200px" src="https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(url)}"></img>\n`
content += qrCode;
})
mdText += content;
});
  fs.writeFile(writePath, mdText, 'utf8', function (error) {
    if (error) {
      console.log('### 写入失败,请排查')
      throw error;
    }
  });
}

 是不是很简单,嘿嘿。

 目前所有困难点已经被我们解决,可以执行VuePress的命令进行预览了~

 若预览没有问题,接下来可以选择部署在自己的服务器上,结束。

结语


 其实每一个实现逻辑都不是卡点,卡点往往来自于没有方向。

 就如这个npm包为例,之前我很迷茫,不知道如何设计,且总想着一次就实现地特别完美。这样的想法也导致我无从下手。

 后来,我改变了方向。首先确定目标:搭建一个npm包。那么实现目标需要什么?

 npm账号、代码仓库、构建工具、使用文档、测试页面、服务器、Ts支持、eslint、单元测试、容错机制等等。

 太多了,以我目前的能力是一时无法完成的,所以在目标不变的前提下,可以选择关键节点实现,先将主流程跑通,后期再慢慢的添砖加瓦,这是不冲突的。

 希望此系列能带给大家一些收获,感谢阅读。

获取知识、沉淀总结、分享经验、收获快乐。