思考: 经过大量的实践发现,编写的文档其中大部分和TypeScript代码中的注释非常相关,越来越多的认可到Docs as Code 的编码概念。如果整个TypeScript项目中能够遵循一种注释规范,同时如果能够根据这种注释规范生成对应的使用文档,并且将这种能力集成到项目的CI/CD中,将会对项目开发效率和迭代效果产生非常大的帮助
/**
* Binds an option to the given property. Does not register the option.
*
* @since v0.16.3
*/
export function BindOption<K extends keyof TypeDocOptionMap>(
name: K
): <IK extends PropertyKey>(
target: ({ application: Application } | { options: Options }) &
{ [K2 in IK]: TypeDocOptionValues[K] },
key: IK
) => void;
/**
* Binds an option to the given property. Does not register the option.
* @since v0.16.3
*
* @privateRemarks
* This overload is intended for plugin use only with looser type checks. Do not use internally.
*/
export function BindOption(
name: NeverIfInternal<string>
): (
target: { application: Application } | { options: Options },
key: PropertyKey
) => void;
export function BindOption(name: string) {
return function (
target: { application: Application } | { options: Options },
key: PropertyKey
) {
Object.defineProperty(target, key, {
get(this: { application: Application } | { options: Options }) {
if ("options" in this) {
return this.options.getValue(name as keyof TypeDocOptions);
} else {
return this.application.options.getValue(
name as keyof TypeDocOptions
);
}
},
enumerable: true,
configurable: true,
});
};
}
2)Flag操作函数
// T & {} reduces inference priority
export function removeFlag<T extends number>(flag: T, remove: T & {}): T {
return ((flag ^ remove) & flag) as T;
}
export function hasAllFlags(flags: number, check: number): boolean {
return (flags & check) === check;
}
export function hasAnyFlag(flags: number, check: number): boolean {
return (flags & check) !== 0;
}
// 结合形成一组类型
const commonFlags = ts.SymbolFlags.Transient |
ts.SymbolFlags.Assignment |
ts.SymbolFlags.Optional |
ts.SymbolFlags.Prototype
1. 前言&背景
背景: 随着团队越来越的场景使用TypeScript开发需要持续维护和迭代的项目,针对这些提供给二方使用的工具库或者组件库,其中文档编写成为一个必要的步骤,刚开始通过md来描述二方使用文档,但是随着项目的迭代,多个成员的参与开发,项目文档的规范越来越不一致或者更新不及时,逐渐项目文档和项目代码脱轨,不利于整个项目的迭代
思考: 经过大量的实践发现,编写的文档其中大部分和TypeScript代码中的注释非常相关,越来越多的认可到Docs as Code 的编码概念。如果整个TypeScript项目中能够遵循一种注释规范,同时如果能够根据这种注释规范生成对应的使用文档,并且将这种能力集成到项目的CI/CD中,将会对项目开发效率和迭代效果产生非常大的帮助
尝试: 针对业务经常使用的 utils 进行尝试实践文档生成,整个项目的代码注释规范统一遵循tsdoc,使用社区提供的TypeDoc方案完成文档的生成,同时将文档生成的过程集成到项目开发的CI/CD流程中,其中的实践项目onex-utils的通过TypeDoc生成的文档可以访问此地址。随着越来越多的项目迭代变更,也越发认同TypeDoc规范文档的生成的价值。同时也针对文档生成有更多的诉求,也为了更好地使用和理解整个TypeDoc的功能,阅读了其整个源码,通过自己的理解,将其中的代码执行流程、代码设计思路、优秀的编码思路抽离出来进行分析。同时也会结合onex-utils针对文档生成的诉求,提供一些TypeDoc插件化改造的思路。本文主要是源码分析和开发记录
2. 运行逻辑
--plugin
was used, loads only those specified plugins, otherwise loads all npm plugins with thetypedocplugin
keyword.src/lib/models
3. 项目文件树
4. 核心思路
整个项目的核心运行逻辑在第一部分已经清晰的进行了梳理,通过阅读源码谈下项目的代码设计亮点和编码中看到设计优秀的代码进行介绍
1) · 代码组织形式
整个项目通过继承和组合的方式将各个组件(功能)进行组合,所以整个项目的运行可以理解为一棵树结构,每颗树节点代表的一个功能,一个大的功能由多个子组件(功能)组合实现。同时由于树的组织结构,每个节点中都可以快速访问子节点的任意或者父节点,整体结构会比较灵活,也符合架构设计中的开放封闭原则。下文也比较硬核,会直接抽离并梳理源码中关于代码组织形式设计的基类的代码。
1. 简介
this.addComponent
添加子组件实例childClass
来定义子组件类型,通过装饰器让继承childClass
的类,都随着父组件(宿主组件)实例化进行子组件的实例化2. 基类实现
3. 使用方式
4. 个人理解
通过这种代码组织形式,可以非常好的将项目的每部分整理成为一颗树,尤其是涉及到其中的插件机制,会让插件的开发变得非常的灵活和方便,子组件可以通过这种方式访问到整个应用的任何一个组件。
BindOptions
装饰器,快速绑定到某个分层中,BindOptions
装饰器源码会发在代码摘录部分展示2) · 插件机制
1.简介
typeDoc
的插件机制主要是依赖事件监听,代码逻辑运行过程中,将其中关键步骤通过事件的形式通知给插件,同时由于TypeDoc
的结构组织形式(前文提到的),事件触发可以根据树的维度进行监听和触发,其中TypeDoc
组件在Converter
节点中事件列表:2. 核心机制
application
(顶层根节点)实例,通过这个实例可以监听代码运行的任何一处(前提是知道整个代码组织形式)initialize
方法,在这个方法中插件会监听宿主或者Application
上的事件,宿主组件触发对应的事件,插件监听宿主组件运行过程中抛出的事件进行修改Converter
中的相关处理
3) · Flags标识
1. 介绍
结合位运算,能够实现非常快速的判断逻辑,例如,remove、havaAll、haveAny
2. 判断逻辑
4) 实现的Event机制
1. 思路
2. 实现
3. 个人理解
id
的标识符,后续也只能触发在当期实例id上绑定的事件队列
5) · 生成Reflection对象
export
的symbol
export
的symbol
分析获取对应type
及其typeParameters
(使用Typescript Compiler API处理)export
中的symbol
中的所有声明转化成到Reflection对象中6) · Reflection 输出
1. 序列化对象输出 (强依赖Reflection结构)
Reflection
中的部分属性抽离出来生成自己的序列化对象ProjectReflection
出发,将对象进行遍历转化2. HTML输出(存在Theme的概念)
Render
文件之后生成具体的Content
,将具体的内容写入到文件夹中,最终生成整个文档5. 代码摘录
1) · 参数绑定装饰器:
2)Flag操作函数
6. 插件化改造
1)短名称实现
描述:生成文档的页面是根据文件的目录生成的,但是针对onex-utils这个项目,整个项目路径没有什么意义,所以希望值保留关键的信息进行展示,首先onex-utils项目文件树如下,其中合理性的路径名称应该是src/utils 下一级具体的group名称和功能名称。
思路:这个改造应该是很简单,只需要处理下生成Reflection对上中的某个名称字段,替换前置的src/utils 字符,这里也演示下如何实现一个插件(全局配置注入)
实现:
2)版本记录
描述:onex-utils是一个工具库,随着不断叠加新功能上线,每个新功能都会有版本支持的概念,如果随着每次发版本记录每个功能的版本,并将这个版本字段渲染到生成的文档中? 思路:如果需要生成版本记录的话,肯定需要有一个版本持久化记录的地方,那么可以使用文件存储或者数据库存储来进行持久化存储,综合考虑下,最终还是选择将版本记录作为静态文件存储到生成的文档中,每次重新发版的时候,会通过https请求访问上一次静态版本数据。然后刷新这份文件,重新进行上传。 实现:
7. 参考资料