探索组件库设计的无限可能性

[IT科技类资讯] 时间:2025-11-05 02:25:26 来源:益强IT技术网 作者:数据库 点击:16次

1. 前言

当前端开发团队面临业务的探索不断增长和项目数量的持续增加时,两个主要问题逐渐凸显:1.业务组件复用的挑战;2. 代码一致性和质量维护问题。前端团队为了解决这些问题,组件通常会选择构建业务组件库。其主要目标是库设

提高开发效率:开发人员可以在不同项目中复用组件,从而减少重复工作,提高开发效率。保持一致的无限代码实现:可以确保在不同项目中使用相同的代码实现,避免了风格不一致和质量差异。质量保障:组件库中的探索组件经过严格验证和测试,能够提供高质量的代码。易于维护和升级:作为独立的组件模块,业务组件库更容易进行维护和升级,使开发人员能够更专注于组件库本身的质量。知识共享和技术积累:组件库可以成为团队共享技术知识和经验的库设平台,帮助提升整体的技术水平。

因此,无限构建业务组件库有助于解决业务组件复用、代码统一性和质量维护等问题,探索为不断发展的业务环境提供了高效、统一和可维护的组件开发流程。

2. 实现分析

在构建业务组件库时,库设需要深入调研和选择适当的技术方案,验证方案的可行性,最终将各个解决方案集成到一起,以实现高效的组件库开发和维护。下面我们将通过整体架构、无限构建、探索质量监控、组件站点搭建、库设组件质量、组件SOP这六大模块对我们的WordPress模板业务组件库进行分析。

图片

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();1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.

测试对比两个打包工具对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 }) ] }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.

4.2 版本控制

组件库发布版本号的管理是很重要的,如何来维护我们的版本号?只能动手在package.json中修改吗?其实可以在打包执行命令的时候,通过命令及参数帮助我们实现自动升级版本号的目的。比如我们在打测试环境包的时候可以使用(cross-env用来指定变量NODE_ENV的值)。

复制"scripts": { "test": "npm version patch && cross-env NODE_ENV=testing node build/build.js" }1.2.3.

下面我们来看看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" },1.2.3.4.5.6.7.

4.3发布

npm包发布使用之家npm进行发布,发布流程如下:

复制1. 首先需要配置私有包,配置一次即可 $ npm config set @auto:ZjDesign http://xxxx.com/ 2. 使用如下命令在私有仓库中添加用户(配置一次即可) npm adduser --registry http://xxxx.com/ 3. 执行打包命令 npm run rollup 4.私有包发布 npm publish --registry http://xxxx.com/1.2.3.4.5.6.7.8.9.10.11.12.13.14.

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.2Docs插件

作用:将单例中的.vue文件中docs标签数据进行格式处理,docs插件流程图。

图片

实现代码:

复制export default (options: Options = {}): Plugin => { const { root, markdown } = options const 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 } } } } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.

vueToMarkdown函数实现:这里面使用了lru-cache进行缓存处理,对于已经解析完成的文件进行跟踪,这样可以加快文档展示。通过fetchCode方法对自定义标签内容进行获取。

5.3 MarkDown插件

作用:将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 it return { code: (await markdownToVue(code, id)).vueSrc, map: null }; } }, }; };1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.

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-const let { html, data } = md.render(content) // avoid env variables being replaced by vite html = 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.vueCode ? await 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 } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.

6. 组件沉淀-SOP

在开发组件并将其沉淀为组件库时,建立合适的SOP机制可以提高开发效率、保持一致性,并促进团队合作。以下是从组件设计到沟通、开发、沉淀为组件库的SOP机制:

图片

► 组件设计:

设计同学进行界面设计,定义组件统一规范。根据多个业务方进行公共组件的提取,确定组件的用途、功能。

►评审:

设计同学和研发同学进行组件设计UI的评审,研发同学定义组件的输入和输出,以及可能的配置项。并且进行编写组件的详细需求文档,包括示例代码和用法示例。

► 开发阶段:

根据组件设计和需求文档,进行组件的开发。使用规范的编码风格和最佳实践。在开发过程中进行单元测试和集成测试,确保组件的稳定性和正确性。

► 文档编写:

编写组件的文档,包括组件的用途、API文档、示例代码等。提供详细的使用说明,帮助其他开发人员使用组件。

► CODE-REVIEW:

使用版本管理工具(如Git)来管理组件的代码。进行代码审查,确保代码质量和一致性。

► 测试与验收:

在真实项目中测试组件,确认其在不同场景下的稳定性和可用性。进行验收测试,确保组件满足预期要求。设计同学进行UI验收。

► 发布:

根据版本号规则,发布组件库的新版本。定期更新组件库,修复bug、添加新功能等。

7. 总结

目前,ZjDesign业务组件库正在不断丰富中。我们努力开发具有高扩展性和低上手成本的组件。并且组件库已有多个新项目接入,整体开发效率明显提升,减少了重复开发。组件库的搭建为团队提供了一个统一的技术平台,促进了知识分享和合作。这一系列改进加速了产品交付,并推动了整体开发流程的提升。

作者简介

何彪

■ 主机厂事业部-技术部-数科技术团队

■2023年2月加入汽车之家,目前任职于主机厂事业部-技术部-数科技术团队,主要负责数科前端业务,组件库搭建等工作。

(责任编辑:IT科技)

    相关内容
    精彩推荐
    热门点击
    友情链接