背景
随着小程序业务的不断迭代,组件越来越多,导致组件规划不清晰、复用率较低、组件重复和代码混乱,以及还有其他如UI交互一致性和提升效率等需求。
对于组件库来说,实现功能重要,而清晰的文档则更加重要,不然因为团队沟通协调成本高,还是会造成各自为战的情况,不能很好的解决组件重复和代码混乱的问题。
抽离封装电动车Taro组件库以及组件库在线演示文档解决上述问题。
方案选择
在文档这一块,PC和H5都有比较成熟的方案框架,如dumi、VitePress等,但Taro这一块还没有成熟的方案框架,即使taro官方提供的taro-ui,组件也不够丰富,而且说明和示例不对应,增加了使用者的学习成本。
所以我们选择自己手动搭建组件库。
手动搭建还有一个优势,像dumi这种框架虽然成熟,但整体很重,里面封装了过多的功能,而且大部分代码是黑盒的。对于大多数团队,只需要使用其部分核心功能,然而做减法是困难且容易出错的。框架代码的黑盒也导致后续维护困难。而手动搭建虽然一开始工作比较多,但是后续维护非常容易。
主要模块
技术栈对于一套组件库,主要模块包括组件、文档、示例三大部分:
组件:es模块输出和按需加载是必须,rollup是最佳选择,组件编写方面,和业务项目保持一致,使用React,组件更适合使用hooks语法,然后ts,less这种就是常规项。
示例:这里是Taro组件库和常规组件库最大的区别,文档是运行在浏览器环境里的,所以想要组件demo可以展示,则需要使用Taro框架的打包h5功能。所以单独起一个项目用来跑demo代码,使用Taro React。至于怎么把文档里的代码引入到demo项目运行,则是使用webpack-chain自定义md文件的loader,自己写一个loader去实现。
文档:文档就是个静态页面,构建工具就选择vite,配置简单,编译速度快。markdown内容部分,使用react-markdown等插件渲染,根据路由读取不同文件即可。右侧预览部分使用iframe加载demo项目。
目录结构
├─config
│ ├─vite // vite配置
│ └─rollup // rollup配置
├─dist // doc打包产物
├─docs-dist // 文档打包产物
├─packages
│ ├─demo // 组件Taro demo项目
│ ├─doc // api文档项目
│ └─ui // 组件库
├─tests
├─index.html // doc入口文件
└─package.json
开发dev流程&路由关系
以增加一个标签组件tag为例:在ui/components下新增tag文件夹,组件入口tag/index.tsx,文档文件tag/README.md,组件打包产物 /dist/tag/index.js;
doc项目增加对应的路由 /tag,路由渲染菜单到左侧,当路由切换到 /tag 时,通过路由组合出import url引入 tag/README.md 文件,通过vite框架的 ?raw引入方式,读取md文件中的内容为字符串传递给 react-markdown 组件,渲染成中间的文档页面;
demo项目独立运行,打包成h5页面,通过iframe嵌入到右侧,先增加与组件路由对应的页面文件,当doc的路由变化时,iframe的路由也同步变化,demo切换到对应的路由页面,然后就可以读取对应的 tag/README.md;
demo项目通过自定义markdwon-loader读取 tag/README.md 文件中写的示例代码,对于组件的引用,通过配置webpack路径别名的方式,把@hb/rent-taro-components 指向组件产物 /dist/tag/index.js;
demo项目通过自定义markdwon-loader 把当前文档中全部示例代码组合成一个可运行页面输出,即可实时预览到全部示例代码的运行。
这套架构的优点
- 文档和demo在一个md文件输出,维护方便且灵活
- 多个demo代码单独编写,互不影响,清晰简洁
- 右侧demo项目独立,如有需要,可单独发布demo
- 小程序各模块功能清晰无黑盒,后续维护容易
各模块说明
doc
doc目录结构
├─src
│ ├─components
│ │ ├─markdown-render // md文件渲染组件
│ │ └─... // 其他布局组件
│ ├─guides // 指南文档
│ ├─router
│ │ ├─comp-doc.ts // 组件路由
│ │ ├─guide-doc.ts // 指南路由
│ │ └─index.ts // 统一导出
│ ├─app.less
│ └─App.tsx // 主页面
└─main.tsx // 入口文件
router
comp-doc.ts:
import Button from '@ui/button/README.md?raw';
import Tag from '@ui/tag/README.md?raw';
const compRoutes = [
{
name: '基础组件',
path: '/basic',
children: [
{
name: '按钮',
path: '/button',
component: Button,
},
{
name: '标签',
path: '/tag',
component: Tag,
},
],
},
];
export default compRoutes;
demo
demo目录结构
除了增加了一个自定义loader,其他文件结构和标准Taro项目完全一致。
├─config
│ ├─dev.js
│ ├─index.js // Taro webpack配置
│ ├─markdownloader.js // 自定义md文件loader
│ └─prod.js
├─src
│ ├─pages
│ │ └─basic // 文件夹路径与doc路由保持一致
│ │ ├─button
│ │ │ ├─index.config.ts
│ │ │ └─index.tsx
│ │ └─tag
│ ├─app.config.ts // 路由配置
│ ├─app.less
│ ├─app.ts
│ └─index.html
└─package.json
route&核心代码
app.config.ts:
除了 /index 部分,路由与doc路由一一对应。
export default {
pages: [
'pages/basic/button/index', // pages和index之间的部分对应doc路由
'pages/basic/tag/index'
],
...
}
pages/basic/button/index.tsx:
只需要把 md文件当做组件引入并 export即可,md 经 markdown-loader 解析后就是一个可运行组件。
// 直接引入组件库中README.md作为demo组件,代码解析在自定义loader中完成
import Demo from '@components/button/README.md';
export default Demo;
config/index.js:
通过 webpackChain 自定义loader。
const config = {
h5: {
// ...
webpackChain (chain, webpack) {
chain.merge({
module: {
rule: {
mdLoader: {
test: /\.md$/,
use: [
{
loader: 'babel-loader',
options: {}
},
{
// 引入自定义loader
loader: `${path.join(__dirname, './markdownLoader.js')}`,
options: {}
},
]
}
}
}
})
}
// ...
}
}
ui
ts文件配置
通过rollup-plugin-typescript2配置对应的config文件。
import RollupTypescript from 'rollup-plugin-typescript2';
// ...
plugins: [
// ...
RollupTypescript({
tsconfig: resolveFile('config/tsconfig.rollup.json'),
}),
]
按需加载
- 多入口打包
- 通过rollup-plugin-postcss抽离样式文件
- 通过rollup-plugin-copy直接移动less文件到dist(因为对于业务项目,不需要打包成css)
import RollupCopy from 'rollup-plugin-copy';
import RollupPostCss from 'rollup-plugin-postcss';
const inputD = {};
compFiles.forEach((item) => {
const val = item.replace(cwd, '').replace('/packages/ui/src/components/', '').replace('/index.tsx', '');
inputD[`${val}`] = item;
});
const config = {
input: inputD,
plugins: [
RollupPostCss({
use: [
[
'less',
{
javascriptEnabled: true,
},
],
],
extract: 'index.less',
extensions: ['.css', '.less'],
makeAbsoluteExternalsRelative: false,
}),
RollupCopy({
verbose: true,
targets: [{
src: resolveFile('/packages/ui/src/components/*/*.less'),
dest: resolveFile('/dist/styles'),
}],
}),
]
}
(本文作者:范翔宇)
本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者使用。非商业目的转载或使用本文内容,敬请注明“内容转载自哈啰技术团队”。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。