总篇227篇 2023年第41篇1.?前言当前端开发团队面临业务的不断增长和项目数量的持续增加时,两个主要问题逐渐凸显:1.业务组件复用的挑战;2. 代码一致性和质量维护问题。前端团队为了解决这些问题,通常会选择构建业务组件库。其主要目标是:提高开发效率:开发人员可以在不同项目中复用组件,从而减少重复工作,提高开发效率。保持一致的代码实现:可以确保在不同项目中使用相同的代码实现,避免了风格不一致和质量差异。质量保障:组件库中的组件经过严格验证和测试,能够提供高质量的代码。易于维护和升级:作为独立的模块,业务组件库更容易进行维护和升级,使开发人员能够更专注于组件库本身的质量。知识共享和技术积累:组件库可以成为团队共享技术知识和经验的平台,帮助提升整体的技术水平。因此,构建业务组件库有助于解决业务组件复用、代码统一性和质量维护等问题,为不断发展的业务环境提供了高效、统一和可维护的开发流程。2.?实现分析在构建业务组件库时,需要深入调研和选择适当的技术方案,验证方案的可行性,最终将各个解决方案集成到一起,以实现高效的组件库开发和维护。下面我们将通过整体架构、构建、质量监控、站点搭建、组件质量、组件SOP这六大模块对我们的业务组件库进行分析。3.?整体架构设计对于业务组件库的整体架构设计而言,核心问题是业务组件库的代码时如何来组织和管理。首先,我们把代码仓库建好。业界一般会把同一类组件库用单个仓库的形式维护,并且把组件开发成NPM包的形式,这里的重点是,你要考虑把所有的组件打包成一个大的NPM包,还是分割是一个个独立的小NPM包。不要小看这个问题,这两种选择会使仓库的目录结构有不小的差异,进一步又会影响到后面组件的开发,构建,发布,实现的技术设计。业界组件库的架构常见单包和多包两种。单包适合简单场景,组件集中在一个库中。多包则将组件分成独立包,适应多项目需求,增强灵活性和复用性。3.1单包是什么把所有的组件看成一个整体,一起打包发布。单个仓库,单个包,统一维护统一管理。? 优点可以通过相对路径实现组件与组件之间的引用,公共代码之间的引用维护成本低,只维护一套package.json配置? 缺点组件完全耦合,必须把它作为一个整体统一发包,就算只改一个非常小的功能,都要对整个包发布更新搭建场景重复打包比如说Antd,它就是作为一个整体的包来尽进行管理的。选择使用单包架构的话,那么你就必须提供按需加载的能力,以降低使用者的成本,你可以考虑支持ESModules的Treeshaking的功能来实现按需加载的能力。当然你也可以选择另外一种方案,叫做"多包架构"。3.2多包是什么每个组件彼此独立,单独打包发布,单个仓库多个包,统一维护单独管理。? 优点组件发布灵活,并且大然支持按需使用? 缺点维护成本高,每个组件都需要一套package配置。组件与组件之间物理隔离,对于相互依赖,公共代码抽象等场景,就只能通过NPM包引用的方式来实现。多依赖多版本问题常用逻辑片段/各个组件都需要的依赖和逻辑在这些场景下的开发统一发布,相对来说没那么方便,多包架构在业界称之为"Monorepo"。3.3结论ZjDesign组件库使用场景比较特殊,组件之间的依赖关系比较强,互相会组合形式新的组件,所以在这里选用的单包开发模式进行开发。单包开发模式可以减少我们开发维护成本,开发工作量的减少,提升组件之间的引用效率。4.?组件库构建当你确定了整体架构之后,就可以开始具体的功能点实现了。业务组件库要求整体框架提供基础的技术能力。4.1项目打包提到构建工具,大家肯定一下会想到很多一堆工具:webapck、gulp、rollup等。网上有很多文章分析它们分别更适合哪些场景,webpack更适合打包组件库、应用程序之类的应用,而rollup更适合打包纯js的类库。下面我们来对比一下webpack和rollup两者的区别。?rollup使用流程无需考虑浏览器兼容问题,开发者写esm代码?->?rollup通过入口,递归识别esm模块?->最终打包成一个或多个bundle.js?->?浏览器直接可以支持引入需考虑浏览器兼容问题,可能会比较复杂,需要用额外的polyfill库,或结合webpack使用打包成npm包:开发者写esm代码?->?rollup通过入口,递归识别esm模块?->?(可以支持配置输出多种格式的模块,如esm、cjs、umd、amd)最终打包成一个或多个bundle.js(开发者要写cjs也可以,需要插件@rollup/plugin-commonjs)初步看来很明显,rollup?比较适合打包js库(react、vue等的源代码库都是rollup打包的)或?高版本无需往下兼容的浏览器应用程序(现在2022年了,时间越往后,迁移到rollup会越多,猜测)这样打包出来的库,可以充分使用上esm的tree?shaking,使源库体积最小?webpack和rollup打包比对let?foo?=?()?=>?{?let?x?=?1;?if?(false)?{?console.log("never?reached");?}?let?a?=?3;?return?a;};let?baz?=?()?=>?{?var?x?=?1;?console.log(x);?post();?function?unused()?{?return?5;?}?return?x;?let?c?=?x?+?3;?return?c;};baz();测试对比两个打包工具对Dead?Code的打包结果?打包对比结果:中间是源代码,左边是rollup打包结果,右边是webpack打包结果对比。webpack打包效果(有很多注入代码)实际上,我们自己写的代码在最下面。上面注入的大段代码?都是webpack自己的兼容代码,目的是自己实现require,modules.exports,export,让浏览器可以兼容cjs和esm语法可以理解为,webpack自己实现polyfill支持模块语法,rollup是利用高版本浏览器原生支持esm(所以rollup无需代码注入)?具体细节rollup和webapck的源码实现差异在这里不做过多赘述,大家可以自己深入研究。? 构建出?esm、cjs?格式选择Rollup来打包组件库,需要有几点注意:配置包格式为?esm、cjs、umdexternal?掉vue,组件库不建议将 Vue?打包进去rollup 配置如下:{input:?file,output:?{compact:?true,file:`lib/index.js`,format:?'es',name,sourcemap:?false,globals:?{echarts:?'echarts',vue:?'Vue'}},external:?['echarts',?'vue'],plugins:?[replace({'process.env.NODE_ENV':?JSON.stringify('production')}),vue({css:?false,template:?{isProduction:?true},modules:?true,styles:?{scoped:?true,trim:?true}}),postcss({extract:?true,modules:?false,scoped:?true,sourceMap:?false,autoModules:?true,plugins:?[simplevars(),nested(),cssnano(),base64({extensions:?['.png',?'.jpeg',?'.jpg',?'.gif'],root:?'./assets/'}),autoprefixer({add:?true})],extensions:?['.css',?'.less'],use:?{less:?{javascriptEnabled:?true}}}),babel({runtimeHelpers:?true,exclude:?'node_modules/**',plugins:?[['@babel/plugin-proposal-optional-chaining',?{?loose:?false?}]],presets:?[['@babel/preset-env',?{?targets:?'>?0.25%,?not?dead'?}]]}),url({limit:?10?*?1024,emitFiles:?true}),progress(),buble({transforms:?{?forOf:?false?}}),uglify({ie8:?true})]}4.2?版本控制组件库发布版本号的管理是很重要的,如何来维护我们的版本号?只能动手在package.json中修改吗?其实可以在打包执行命令的时候,通过命令及参数帮助我们实现自动升级版本号的目的。比如我们在打测试环境包的时候可以使用(cross-env用来指定变量NODE_ENV的值)。"scripts":?{"test":?"npm?version?patch?&?cross-env?NODE_ENV=testing?node?build/build.js"}下面我们来看看npm version命令具体的使用方式:npm采用了semver规范作为依赖版本管理方案。semver约定一个包的版本号必须包含3个数字。MAJOR.MINOR.PATCH?意思是?主版本号.小版本号.修订版本号MAJOR?对应大的版本号迭代,做了不兼容旧版的修改时要更新MAJOR版本号MINOR? 对应小版本迭代,发生兼容旧版API的修改或功能更新时,更新MINOR版本号PATCH?对应修订版本号,一般针对修复BUG的版本号当我们每次发布包的时候都需要升级版本号:"scripts":?{"rollup":?"rollup?-c?rollup.config.js","publish:major":?"npm?version?major?&?npm?publish","publish:minor":?"npm?version?minor?&?npm?publish","publish:patch":?"npm?version?patch?&?npm?publish","publish:beta":?"npm?version?prerelease?--preid=beta?&?npm?publish?--tag=beta"},4.3?发布npm包发布使用之家npm进行发布,发布流程如下:1.?首先需要配置私有包,配置一次即可$?npm?config?set?@auto:ZjDesign?
http://xxxx.com/2.?使用如下命令在私有仓库中添加用户(配置一次即可)npm?adduser?--registry?
http://xxxx.com/3.?执行打包命令npm?run?rollup4.私有包发布npm?publish?--registry?
http://xxxx.com/5.?组件搭建实例? ? ? ?首先看一下我们单个组件UI设计图。从图中可以看出,每个组件实例demo可以看成抽象五大模块。1.组件的title+subtitle、2.组件描述、3.多个组件形态展示、4.设计原则与页面布局、5.单个组件形态的代码示例。5.1?组件demo整体目录index.zh-CN.md: 作为静态数据的快速输出,包含组件名称、描述、设计原则和API。从.md格式文件中可以使用插件md(vite插件)解析出组件需要的数据,这个在后面单独讲解实现细节。单个组件类型文件:包含组件排序,title,描述,html。这个通过docs进行数据的解析,具体解析后面进行详细讲解。5.2?Docs插件作用:将单例中的.vue文件中docs标签数据进行格式处理,docs插件流程图。实现代码:export?default?(options:?Options?=?{}):?Plugin?=>?{const?{?root,?markdown?}?=?optionsconst?vueToMarkdown?=?createVueToMarkdownRenderFn(root)const?markdownToVue?=?createMarkdownToVueRenderFn(root,?markdown)return?{name:?'vueToMdToVue',async?transform(code,?id)?{if?(id.endsWith('.vue')?&?id.indexOf('/demo/')?>?-1?&?id.indexOf('index.vue')?===?-1)?{const?res?=?vueToMarkdown(code,?id)return?{code:?res.ignore?res.vueSrc?:?(await?markdownToVue(res.vueSrc,?id)).vueSrc,map:?null}}}}}vueToMarkdown函数实现:这里面使用了lru-cache进行缓存处理,对于已经解析完成的文件进行跟踪,这样可以加快文档展示。通过fetchCode方法对自定义标签内容进行获取。5.3MarkDown插件作用:将markdown文档格式数据转化成我们想要的vue格式化数据。这里主要通过对第三方markdown-it,依据UI设计的要求进行定制化的修改。可以支持输入emoji,anchor,toc分别使用markdown-it-emoji、markdown-it-anchor、markdown-it-table-of-contents插件。md插件实现流程1、定义插件导出,基于vite的Plugin进行封装:import?{?createMarkdownToVueRenderFn?}?from?'./markdownToVue';import?type?{?MarkdownOptions?}?from?'./markdown/markdown';import?type?{?Plugin?}?from?'vite';interface?Options?{root?:?string;markdown?:?MarkdownOptions;}export?default?(options:?Options?=?{}):?Plugin?=>?{const?{?root,?markdown?}?=?options;const?markdownToVue?=?createMarkdownToVueRenderFn(root,?markdown);return?{name:?'mdToVue',async?transform(code,?id)?{if?(id.endsWith('.md'))?{//?transform?.md?files?into?vueSrc?so?plugin-vue?can?handle?itreturn?{?code:?(await?markdownToVue(code,?id)).vueSrc,?map:?null?};}},};};2、markdownToVue核心思想实现:通过lru-cache进行解析文档的缓存处理,使用gray-matter对docs格式数据的解析,最后生成demo-box组件格式的vue文件。export?function?createMarkdownToVueRenderFn(root:?string?=?process.cwd(),?options:?MarkdownOptions?=?{})?{const?md?=?createMarkdownRenderer(options)return?async?(src:?string,?file:?string):?Promise=>?{const?relativePath?=?slash(path.relative(root,?file))const?cached?=?cache.get(src)if?(cached)?{debug(`[cache?hit]?${relativePath}`)return?cached}const?start?=?Date.now()const?{?content,?data:?frontmatter?}?=?matter(src)//?eslint-disable-next-line?prefer-constlet?{?html,?data?}?=?md.render(content)//?avoid?env?variables?being?replaced?by?vitehtml?=?html.replace(/import\.meta/g,?'import.meta').replace(/process\.env/g,?'process.env')//?TODO?validate?data.links?const?pageData:?PageData?=?{title:?inferTitle(frontmatter,?content),description:?inferDescription(frontmatter),frontmatter,headers:?data.headers,relativePath,content:?escapeHtml(content),html,//?TODO?use?git?timestamp?lastUpdated:?Math.round(fs.statSync(file).mtimeMs)}const?newContent?=?data.vueCodeawait?genComponentCode(md,?data,?pageData):?`${html}${fetchCode(content,?'style')}`debug(`[render]?${file}?in?${Date.now()?-?start}ms.`)const?result?=?{vueSrc:?newContent?.trim(),pageData}cache.set(src,?result)return?result}}6.?组件沉淀-SOP在开发组件并将其沉淀为组件库时,建立合适的SOP机制可以提高开发效率、保持一致性,并促进团队合作。以下是从组件设计到沟通、开发、沉淀为组件库的SOP机制:? 组件设计:设计同学进行界面设计,定义组件统一规范。根据多个业务方进行公共组件的提取,确定组件的用途、功能。?评审:设计同学和研发同学进行组件设计UI的评审,研发同学定义组件的输入和输出,以及可能的配置项。并且进行编写组件的详细需求文档,包括示例代码和用法示例。? 开发阶段:根据组件设计和需求文档,进行组件的开发。使用规范的编码风格和最佳实践。在开发过程中进行单元测试和集成测试,确保组件的稳定性和正确性。? 文档编写:编写组件的文档,包括组件的用途、API文档、示例代码等。提供详细的使用说明,帮助其他开发人员使用组件。? CODE-REVIEW:使用版本管理工具(如Git)来管理组件的代码。进行代码审查,确保代码质量和一致性。? 测试与验收:在真实项目中测试组件,确认其在不同场景下的稳定性和可用性。进行验收测试,确保组件满足预期要求。设计同学进行UI验收。? 发布:根据版本号规则,发布组件库的新版本。定期更新组件库,修复bug、添加新功能等。7.?总结目前,ZjDesign业务组件库正在不断丰富中。我们努力开发具有高扩展性和低上手成本的组件。并且组件库已有多个新项目接入,整体开发效率明显提升,减少了重复开发。组件库的搭建为团队提供了一个统一的技术平台,促进了知识分享和合作。这一系列改进加速了产品交付,并推动了整体开发流程的提升。作者简介何彪主机厂事业部-技术部-数科技术团队■2023年2月加入汽车之家,目前任职于主机厂事业部-技术部-数科技术团队,主要负责数科前端业务,组件库搭建等工作。阅读更多:前端工程化实战-开发企业级CLI汽车之家Unity前端通用架构升级实践汽车之家主机厂离线化 H5 Hybrid 实践