组件文档的事情从编写到生成实际使用的基本组件库。在实际工作中,我们总是要根据自己的工作内容,整理出一套适合自己业务风格的组件库。基础组件可以基于以上开源组件库和less框架等多主题风格方案。定制化,更多的是基于这些适合自己业务产品的基础组件,形成一套业务组件库。在开发组件库时,我们可能会选择Monorepo单仓库多包形式(参考网文https://segmentfault.com/a/11...等)或者其他Git多仓库单包形式form来维护组件代码最终难免将组件整理成文档,提供给其他同事参考。本文围绕组件文档的制作,讲述我在制作文档过程中的一系列思考过程,解决组件开发“最后一公里”的体验问题。构件文档的编写规范和构件文档的研究与构建都必须有一定的规范,而规范是任何软件工程阶段的第一步。对于一个组件来说,文档所揭示的内容应该包含哪些内容决定了组件开发者和用户的所有体验。确定这些规格并不困难。参考开源组件库的文档,我们会有一些初步的印象。因为我们团队使用的是React技术栈,所以这里指的是AntDesign组件库。比如最基本的Button组件,官方文档从上到下的内容结构是这样的:显示组件的标题,后面是组件的简短描述。列出组件的使用场景。不同使用场景下的效果演示和代码案例。可以跳出CodeSandbox、CodePen等在线源码编辑网站实时编辑查看效果。列出组件的可配置属性和接口方法。该列表包括属性/方法名称、字段类型、默认值、使用说明等。常见问题FAQ。链接到设计人员的一些案例描述。这些文件内容丰富。作为一个开放的组件库,从设计到开发的角度几乎都考虑到了,用户体验特别好。出于好奇,查看了官网源码库,比如Button组件:https://github.com/ant-design...。在源码库下放置了与组件入口文件同名的.zh-CN.md和.en-US.md后缀的Markdown文件,这些Markdown文件中是官网的内容我们看到的文件...嗯?不行,好像少了什么,案例演示和示例代码呢?难道AntD官网文档是自己手动开发维护的?这么大的工程,肯定不行。根据其官网的访问路径如docs/react/introduce-cn,在源码库中有对应的Markdown文件,官网的文档一定是官方仓库按照某种规范生成的。那么它是如何产生的呢?当我第一次做组件文档规范时,我很感兴趣。作为一个前端工程老手,熟练的打开了它的package.json文件,通过查看里面的scripts命令,轻而易举的找到了site命令(源码仓库package.json):npmrunsite:theme&&cross-envNODE_ICU_DATA=node_modules/full-icuESBUILD=1bishengbuild--ssr-c./site/bisheng.config.js原来网站是用bisheng搭建的。通过查阅必胜的工具库,发现确实是文档系统的自动生成工具。其下有一个插件bisheng-plugin-react,可以将Markdown文档中的JSX源代码块转换成可以运行demo的example。每个组件的示例代码文档都维护在每个组件路径下的demo目录下。emmm,必胜确实很厉害很强大。它还可以支持多种语言。结合一定的文档规范,可以快速搭建文档主站。但是在深入了解bisheng的过程中,我发现它的文档比较缺乏,打包的东西就那么多。使用过程中黑盒感严重,我们团队的组件库需求其实很简单。首先,它可能很方便。流通,但仅供内部流通和使用,不会开源。那么,有没有更简单的方法来构建文档呢?更多文档工具库研究在Google搜索中输入ReactComponentsDocumentation等关键字,我们可以快速搜索到很多与React组件文档相关的工具库。这里看到的有:Docz、StoryBook、ReactStyleguidist、UMI构建系统下的dumi等,这些工具库都支持解析Markdown文档,其中Docz和StoryBook还支持使用mdx格式(Markdown和JSX的混写)),在文档内容格式上,可以支持组件属性列表、示例代码演示等功能。下面我们就简单了解一下这些工具库对组件文档的支持情况。在了解Docz的过程中,我发现Docz其实是一个比较古老的文件系统构建工具。它本身主要推广MDX格式的文档,无需任何配置即可运行。支持本地调试构建生成可发布产品,支持多包仓库、TypeScript环境、CSS处理器、插件机制等,充分满足功能需求。只是Docz似乎只支持React组件(当然对我们来说已经足够了),距离上次更新它的NPM包已经两年了。另外,MDX格式的文档虽然理解成本低,但对于不怎么使用的同事来说还是有一定的接受和熟练成本。暂时可选。当StoryBook第一次了解到StoryBook时,被它的66.7KStar量(Docz是22K)惊呆了。与Docz相比,StoryBook相关的社区内容非常丰富。它不依赖组件的技术堆栈系统。现在支持React,Vue,Angular,WebComponents等几十种技术栈。StoryBook构建文档系统的方式不是自动解析Markdown文件,而是暴露一系列构建文档的接口,让开发者手动为组件一个一个写故事文件,StoryBook会自动解析这些故事文件生成文档内容。这种方式会带来一定的学习和理解接口的成本,但同时,基于这种方式,可以达到支持跨组件技术栈的效果,让社区显得更加丰富。官方示例:https://github.com/storybookj...。StoryBook的强大毋庸置疑,但对于我们团队的情况来说,还是有些大材小用。另外需要额外了解接口功能和编写组件故事文件,这在团队内部很难推广:每个人都很忙,组件开发分布在团队中几十个人。不切实际。继续调查。ReactStyleguidistReactStyleguidist的star量没有StoryBook(10K+)那么耀眼,但是包体的下载量比较大,最近投稿也比较活跃。顾名思义,它支持React组件的环境。它通过自动解析Mardown文件的形式来生成文档。实现方式是自动解析文档中的JSX声明代码块,根据名称一一对应的规则找到组件的源代码,然后通过Webpack将声明的代码块打包生成对应的的演示示例。在继续试用ReactStyleguidist的一些基本案例后,它的一个功能引起了我的注意:它会自动解析组件的属性,并解析出它的类型、默认值、注解描述等,然后解析解析出来的内容内容自动生成的属性表放置在演示示例上方。这有点像JSDOC。对于一个组件开发者来说,TA确实需要关心组件属性的公开、注释、文档案例的写法,但是写完就够了,没必要去想怎么去适配去构建一个文档。系统。此外,ReactStyleguidist基于解析AST并配合工具react-docgen解析组件属性,也支持配合react-docgen-typescript实现在TypeScript环境下解析组件。此外,很多配置项支持更改文档站点相关的各部分的显示风格、内容格式等,配置自定义支持相当灵活。当然它也有一些缺点,比如内嵌Webpack,对于编译组件库的构建工具已经换成Rollup.js的情况来说,是额外的配置负担。总的来说,ReactStyleguidist在我看来是一个小而美的工具库,非常适合我们团队参与人数多,日常开发工作量大的情况。暂时可选。dumi之所以了解dumi,是因为我们团队中的一些组件文档站点都是基于它构建的。Dumi还通过自动解析Markdown文档来构建文档系统。它也基本是零配置,还有很多灵活的配置,支持改变文档站点某些部分的显示内容和(主题)风格。UMI系统的整体风格是:开箱即用,包装精美。它可以单独使用,也可以与UMI框架一起配置。只是和上面学习的ReactStyleguidist相比,没看出有什么明显的优势,好像也没有看到自动解析组件属性的功能。对我来说,ReactStyleguidist下没有亮点。你可以参考一下,不要再考虑了。组件文档的生成对比了多个文档构建的工具库,最终选择了ReactStyleguidist。在我看来,吸引我的自然是基于react-docgen的解析组件属性、类型、注解描述等功能。一系列的规范,另一方面,其API接口的访问形式,通过统一构建和配置,可以统一生产文档内容格式和风格,方便各种业务访问和使用。确定了技术方案之后,就是如何在其基础上实现具体的封装,方便各个业务仓库的接入。我们团队有自己统一的CLI构建工具,ReactStyleguidist的CLI配置多一个在理解上会有一定的熟悉成本,但是我可以基于ReactStyleguidist的NodeAPI访问形式将ReactStyleguidist的功能集成到我们自己的开发和为CLI构建命令。首先,基于ReactStyleguidistAPI的形式,统一一套配置,抽象出生成ReactStyleguidist实例的代码://定义一套统一的配置,生成react-styleguidist实例importstyleguidistfrom'react-styleguidist/lib/脚本/index.esm';从'react-docgen'导入*作为docgen;从'react-docgen-typescript'导入*作为docgenTS;从'react-docgen'导入类型*作为RDocgen;导出类型DocStyleguideOptions={cwd?:细绳;根目录:字符串;工作目录:字符串;customConfig?:object;};constDOC_STYLEGUIDE_DEFAULTS={cwd:process.cwd(),rootDir:process.cwd(),workDir:process.cwd(),customConfig:{},};exportconstcreateDocStyleguide=(环境:'development'|'production',options:DocStyleguideOptions=DOC_STYLEGUIDE_DEFAULTS,)=>{//0.处理配置项constopts={...DOC_STYLEGUIDE_DEFAULTS,...options};const{cwd:cwdPath=DOC_STYLEGUIDE_DEFAULTS.cwd,rootDir,workDir,customConfig,}=opts;//标记:是否正在调试所有包letisDevAllPackages=true;//解析项目根目录包信息constpkgRootJson=Utils.解析包同步(rootDir);//1.解析指定待调试包下的组件letcomponentsPattern:(()=>string[])|字符串|字符串[]=[];if(path.relative(rootdir,workdir).length<=0){//当您选择调试所有软件包时,读取在root路径const{packages=[]}=[]=[]=[]=[]=[]=[]=[]=[]=[]pkgRootJson;componentsPattern=packages.map(packagePattern=>(path.relative(cwdPath,path.join(rootDir,packagePattern,'src/**/[A-Z]*.{js,jsx,ts,tsx}'))));}else{//选择调试某个包,定位到所选特定包下的组件componentsPattern=path.join(workDir,'src/**/[A-Z]*.{js,jsx,ts,tsx}');isDevAllPackages=false;}//2.获取默认的webpack配置constwebpackConfig=getWebpackConfig(env);//3.生成styleguidist配置实例conststyleguide=styleguidist({title:`${pkgRootJson.name}`,//所有要解析的组件components:componentsPattern,//属性解析设置propsParser:(filePath,code,resolver,handlers)=>{if(/\.tsx?/.test(filePath)){//ts文件,使用typescriptdocgen解析器constpkgRootDir=findPackageRootDir(path.dirname(filePath));常量tsConfigParser=docgenTS.withCustomConfig(path.resolve(pkgRootDir,'tsconfig.json'),{},);constparseResults=tsConfigParser.parse(filePath);constparseResult=parseResults[0];返回(parseResultasany)作为RDocgen.DocumentationObject;}//否则使用默认的react-docgen解析器constparseResults=docgen.parse(code,resolver,handlers);如果(Array.isArray(parseResults)){返回parseResults[0];}返回解析结果;},//webpack配置webpackConfig:{...webpackConfig},//是否初始展开代码示例//expand:expand|崩溃:崩溃|隐藏:不显示;exampleMode:'expand',//显示组件路径内容getComponentPathLine:(componentPath)=>{constpkgRootDir=findPackageRootDir(path.dirname(componentPath));试试{constpkgJson=Utils.parsePackageSync(pkgRootDir);constname=path.basename(componentPath,path.extname(componentPath));返回`从'${pkgJson.name}'导入${name};`;}catch(错误){返回组件路径;}},//调试所有包时不显示侧边栏showSidebar:isDevAllPackages,//日志配置logger:{//其中之一:info,debug,warninfo:message=>Utils.log('info',message),warn:message=>Utils.log('warning',message),debug:message=>console.debug(message),},//覆盖自定义配置...customConfig,});返回样式指南;};这样在dev和build命令下就可以分别调用实例server接口方法和build接口方法来实现调试和构建输出文档静态资源//在dev命令下开始调试//0.初始配置constHOST=process.env.HOST||自定义配置服务器主机||'0.0.0.0';constPORT=process.env.PORT||自定义配置.serverPort||'6060';//1.生成样式指南实例conststyleguide=createDocStyleguide('development',{cwd:cwdPath,rootDir:pkgRootPath,workDir:workPath,customConfig:{...customConfig,//devserverhostserverHost:HOST,//devserver端口serverPort:PORT,},},);//2.调用server接口方法开始调试const{compiler}=styleguide.server((err,config)=>{if(err){console.error(err);}else{consturl=`http://${config.serverHost}:${config.serverPort}`;Utils.log('info',`Listeningat${url}`);}});compiler.hooks.done.tap('done',(stats:any)=>{consttimeStr=stats.toString({all:false,timings:true,});conststatStr=stats.toString({all:false,warnings:true,errors:true,});console.log(timeStr);if(stats.hasErrors()){console.log(statStr);返回;}});//在构建命令下执行构建//生成样式指南实例conststyleguide=MonorepoDev.createDocStyleguide('production',{cwd,rootDir,workDir,customConfig:{styleguideDir:path.join(pkgDocsDir,'dist'),},});//构建文档内容awaitnewPromise
