如何使用Monorepo方式构建一个UI组件库基本架子

1,599 阅读5分钟

1.概要

1) 传统UI组件库的缺陷

之前的组件库里面将所有的子组件都放在一个仓库下管理(例如:elementui2), 如果需要在组件库上对某一个小组件进行扩展则必须把整个包下到本地进行然后进行修改, 这个过程比较复杂且增加了心智负担.

2) Monorepo方式解决的问题

一个仓库下有多个包, 它们之间都是相互独立的, 并且每个包都可以单独拿出来使用,例如有一个UI库但是我只想使用其中的Button组件,我可以单独进行引入Buttion子包进行使用而无需安装整个UI库. 这样如果我只想修改其中的Button组件就直接将这个子包拉去下来修改即可.

3) 本文目的

本文是此专栏第一篇文章, 主要是为了能快速了解如何从0开始搭建一个monorepo方式的组件库和构建的一些注意点.


2. Monorepo介绍

image.png

Monorepo是管理项目代码的一个方式, 指在一个项目仓库中管理了多个模块/包(package);

优势:

  1. 一个仓库可维护多个模块, 不用到处找仓库
  2. 方便版本管理和依赖管理, 内部模块之间的引用和调用都非常方便

缺点:

仓库的体积会变大


3. 如何使用Monorepo的方法构建一个组件库框架

3-1首先使用lerna管理工具搭建环境

lerna可以用来管理项目版本和依赖, 这里主要能帮我们快速建立一个Monorepo的项目, 减少我们在配置Monorepo项目时候一些琐碎的配置

1. 全局安装lerna npm i lerna -g

2. 初始化项目 lerna init 然后配置使用yarn安装工具以及工作空间packages

此配置就是为了说明通过yarn来安装依赖以及所有子包都在packages文件夹中

// lerna.json
{
  "$schema": "node_modules/lerna/schemas/lerna-schema.json",
  "version": "0.0.0",
  "useWorkspaces": true, // 说明使用工作区
  "packages": [
    "packages/*"
  ],
  "npmClient": "yarn" // 使用yarn进行安装
}
// package.json
{
  "name": "root",
  "private": true, // 说明是私有包,不会进行发布
  "workspaces": [
    "packages/*" // 工作区文件夹
  ],
  "devDependencies": {
    "lerna": "^6.5.1"
  }
}

3. 进行本地依赖安装 yarn install

3-2 ts环境配置

由于这个组件库是通过Vue3 + TS来编写的所以先要全局安装好TS和Vue相关依赖

1.安装typescript和vue3

 yarn add typescript -W // -W表示安装到根目录下而不是子包目录下
 yarn add vue@next -W // 安装vue3

2.生成tsconfig.json文件并且配置

npx tsc --init 然后配置

"compilerOptions": {
  "target": "ESNext",
  "module": "ESNext",
  // 支持cjs和ejs转化
  "esModuleInterop": true,
  // 跳过类库检测
  "skipLibCheck": true,
  // 强制区分大小写
  "forceConsistentCasingInFileNames": true,
  "moduleResolution": "node",
  // vue有自己的一套jsx解析规则, 这里配置就是为了不需要使用ts的jsx
  "jsx": "preserve",
  // ts打包代码需要有声明文件
  "declaration": true,
  "sourceMap": true
}

3. 配置type类型解析.vue结尾的文件( 防止ts报错 )

作用就是为了解决如 import Button from 'button.vue'报错,帮助ts认识.vue后缀文件

在根目录创建typing/vue-shim.d.ts文件, Vue为了支持ts要求所有组件都通过一个defineComponent函数来包裹, 所以这里可以通过ts内置的ReturnType的方式来获取组件的返回值和类型, 方便在引入组件的时候有更好的提示.

import { defineComponent, App } from 'vue';
declare module '*.vue'{
    const component: ReturnType<typeof defineComponent> & {
        install(app: App):void // 每个组件都有一个install方法进行注册
    }
    export default component
}

3-3 通过lerna创建第一个button组件子包

1.创建Button子包目录

通过命令 lerna create button 命名创建子包信息

image.png

注意: 此时需要进行输入包的name, 需要使用@符表示是一个组织(非常重要), 公司天天口号喊着AI everywhere有被洗脑, 这里简写搞一个@ai-ui/button( -.- ), 其他选项直接跳过即可

2.修改子包目录

通过lerna create button默认产生的文件目录, 由于项目中需要使用Vue3 + TS来编写组件,所以需要对目录进行修改

image.png

修改结果为

image.png

3.button.vue和index.ts的作用

button.vue文件主要是为了实现组件逻辑, 而index.ts则是作为是一个入口来注册该组件

// button.vue 先简单的写一个基础模板表明该文件的用意
<tempalte>
    <button>btn</button>
</tempalte>

<script lang="ts">
    import { defineComponent } from 'vue';
    export default defineComponent({
        name: 'AiButton'
    })
</script>
// index.ts 入口文件(普通版本)
import { App } from 'vue';
import Button from './src/button.vue';

 // 注册组件
Button.install = (app: App):void => {
    app.component(Button.name, Button);
}

export default Button;
// index.ts 通过ts进行类型标注
import {App, defineComponent} from 'vue';
import Button from './src/button.vue';

// 添加install属性方法
Button.install = (app: App): void => {
}


type IWithInstall<T> = T & { install(app: App): void };
const _Button: IWithInstall<typeof Button> = Button;

export default _Button;

3-4 通过lerna创建第二个Icon组件子包

步骤和上面一样

// icon.vue
<template>
    <span>icon组件</span>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
    name: 'AiIcon'
})
</script>

// index.ts 入口
import {App} from 'vue';
import Icon from './src/icon.vue';

Icon.install = (app: App): void => {
    // 注册逻辑
}

type IWithInstall<T> = T & {
    install(app: App): void
}
const _Icon:IWithInstall<typeof Icon> = Icon;
export default _Icon;

3-5 组件库入口处理 ( 重点 )

上面创建了button和icon两个基本的组件, 现在需要一个入口统一进行注册处理

1.创建UI库的根文件目录

注意创建的时候不能有@ai-ui/前缀了,直接使用ai-ui

lerna create ai-ui

2.安装所有的子包

在根目录下执行, node_modules下会产生一个@ai-ui文件夹, 这个就是一个软链接会链接到真正的子目录packages下面, 所以可以在当前任意的子组件里去引入其他的子组件( 内部原理就是根据workspaces这个配置来的 )

yarn install

3. 在packages/ai-ui文件下创建index.ts

处理所有子组件的注册

import { App } from 'vue';
// 会链接到packages目录下button/index.ts
import Button from '@ai-ui/button'; 
import Icon from '@ai-ui/icon';

const subcomponents = [
    Button,
    Icon
]

// 全局注册所有的子组件
const install = (app: App): void => {
    subcomponents.forEach(component => {
        app.component(component.name, component)
    })
}

// 暴露出所有注册方式
export default {
    install
}

3-6 menorepo基本创建流程完成

整体目录

image.png

之后会加入本地webpack调试服务, 方便编写组件时候能在线进行调试

其他

项目地址: github.com/underFyh/AI…

感觉自己写一套组件库很有成就感, 由于经常加班并且自己也是一边学习一边抽空写文章, 所以会不定期更新哈. 如果一直不更新则说明苦逼加班中 -.-!