194 lines
3.6 KiB
TypeScript
194 lines
3.6 KiB
TypeScript
import path from "path";
|
|
import fs from "fs";
|
|
import TsToJson from "./ts2json";
|
|
import toc from "markdown-toc";
|
|
import objHash from "object-hash";
|
|
|
|
const hash = (str: string) => "" + objHash(str).substring(0, 6);
|
|
|
|
/**
|
|
* props 注释元信息
|
|
*/
|
|
interface IPropMeta {
|
|
/**
|
|
* 声明属性是否必须 对应 ts ?
|
|
*/
|
|
required?: boolean;
|
|
/**
|
|
* @name 参数名称
|
|
*/
|
|
name?: string;
|
|
/**
|
|
* @type 类型
|
|
*/
|
|
type?: string;
|
|
/**
|
|
* @description 说明
|
|
*/
|
|
description?: string;
|
|
/**
|
|
* @default 默认值
|
|
*/
|
|
default?: string;
|
|
/**
|
|
* @version 版本
|
|
*/
|
|
version?: string | number;
|
|
}
|
|
|
|
interface IProp2jsonItem {
|
|
[key: string]: {
|
|
props: IPropMeta[];
|
|
};
|
|
}
|
|
|
|
interface IRoute {
|
|
active: false;
|
|
path: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface ITree {
|
|
/**
|
|
* 组件🎄
|
|
*/
|
|
compoTree: ICompoTree;
|
|
/**
|
|
* 文件🎄,最小粒度更新
|
|
*/
|
|
fileTree?: {};
|
|
/**
|
|
* 路由🎄,根据 menuTree 生成
|
|
*/
|
|
routerList: IRoute[];
|
|
/**
|
|
* 插件配置
|
|
*/
|
|
config?: {};
|
|
}
|
|
|
|
interface ITocItem {
|
|
/**
|
|
* 锚点的 hash
|
|
*/
|
|
anchor?: string;
|
|
/**
|
|
* mardkown h标签 innerHTML
|
|
*/
|
|
content: string;
|
|
i: 0;
|
|
lvl: 1;
|
|
seen: 0;
|
|
/**
|
|
* 小写 innerHTML
|
|
*/
|
|
slug: "hi";
|
|
}
|
|
|
|
/**
|
|
* ts interface 转 json
|
|
*/
|
|
const tsParser = new TsToJson();
|
|
/**
|
|
* 组件开发目录
|
|
*/
|
|
const root = path.resolve(process.cwd(), "src/components");
|
|
/**
|
|
* 文件映射,最小粒度更新 For HMR
|
|
*/
|
|
const fileTree = {};
|
|
|
|
/**
|
|
* ICompoTree Item
|
|
*/
|
|
interface ICompoMetadata {
|
|
/**
|
|
* 组件 props 信息
|
|
*/
|
|
props: IPropMeta[] | [];
|
|
/**
|
|
* markdown 文件目录 table of content
|
|
*/
|
|
toc: ITocItem[];
|
|
}
|
|
|
|
/**
|
|
* 组件🎄
|
|
*/
|
|
interface ICompoTree {
|
|
[key: string]: ICompoMetadata;
|
|
}
|
|
|
|
/**
|
|
* 解析 markdown 内容生成目录 table of content
|
|
* @param {string} content markdown 内容
|
|
*/
|
|
const md2toc = (content: string) => {
|
|
if (!content) return [];
|
|
return toc(content).json;
|
|
};
|
|
|
|
/**
|
|
* 生成路由
|
|
* @param {ICompoTree} compoTree 菜单
|
|
*/
|
|
const processRouter = (compoTree: ICompoTree): IRoute[] => {
|
|
return Object.keys(compoTree).map((t) => ({
|
|
active: false,
|
|
path: t.toLowerCase(),
|
|
name: t,
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* props.ts 转换成 json 对象
|
|
* @param {string} path 组件 props.ts 文件路径
|
|
* @returns
|
|
*/
|
|
const parsePropsTs = (path: string) => {
|
|
const prop2json: IProp2jsonItem = fs.existsSync(path)
|
|
? tsParser.parse(path)
|
|
: {};
|
|
return Object.values(prop2json)?.[0]?.props ?? [];
|
|
};
|
|
|
|
/**
|
|
* 解析 mdx 文件中的目录
|
|
* @param {string} path 组件 mdx 文件路径
|
|
*/
|
|
const parseToc = (path: string) => {
|
|
const isFile = fs.statSync(path).isFile();
|
|
const mdxContent = isFile ? fs.readFileSync(path).toString() : "";
|
|
// 注入锚点 hash
|
|
const toc = md2toc(mdxContent).map((i) => ({
|
|
...i,
|
|
anchor: `anchor-${hash(i.content)}`,
|
|
}));
|
|
return toc;
|
|
};
|
|
|
|
const walkCompoMetadata = (dirs: string[]): ICompoTree => {
|
|
const compoTree: ICompoTree = {};
|
|
dirs.forEach((dir) => {
|
|
const mdxFile = path.resolve(root, dir, "index.mdx");
|
|
const propFile = path.resolve(root, dir, "props.ts");
|
|
fileTree[mdxFile] = dir;
|
|
fileTree[propFile] = dir;
|
|
const toc = parseToc(mdxFile);
|
|
const props = parsePropsTs(propFile);
|
|
compoTree[dir] = { toc, props };
|
|
});
|
|
return compoTree;
|
|
};
|
|
|
|
/**
|
|
* 初始化
|
|
* @returns menuTree: 目录树, 包含markdownHeadings | propsTree: 组件 api 树
|
|
*/
|
|
export const initialize = (): ITree => {
|
|
const dirs = fs.existsSync(root) ? fs.readdirSync(root) : [];
|
|
const compoTree = walkCompoMetadata(dirs);
|
|
const routerList = processRouter(compoTree);
|
|
return { compoTree, fileTree, routerList };
|
|
};
|