背景
随着小程序业务的一直迭代,组件越来越多,导致组件布局不清晰、复用率较低、组件反复和代码凌乱,以及还有其余如 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'),
}],
}),
]
}
(本文作者:范翔宇)
本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者应用。非商业目标转载或应用本文内容,敬请注明“内容转载自哈啰技术团队”。