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 }; };