乐趣区

关于前端:哈啰电动车Taro多端组件库实践

背景

随着小程序业务的一直迭代,组件越来越多,导致组件布局不清晰、复用率较低、组件反复和代码凌乱,以及还有其余如 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'),
      }],
    }),
  ]
}

(本文作者:范翔宇)

本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者应用。非商业目标转载或应用本文内容,敬请注明“内容转载自哈啰技术团队”。

退出移动版