前端搞基建-业务组件库

3,265 阅读5分钟

组件开发作为基建项目也有一年的时间了,这一年对于组件开发总结了一些心得分享给大家,希望对大家有一定帮助

1.组件开发(ts)

1.1 初始化项目

使用vue-cli3直接生成项目,非常便捷,不用自己配一些基础环境,当然你也可以自己搭建不使用webapck使用rollup,可以使代码更加清亮


1.2 组件库项目配置

生成完基本的架子之后需要对项目做一些基本的配置,对照一些大厂的文件夹命名


@types 定义全局的ts声明文件

example 组件库 demoui

lib 组件库打包后的代码

packages 组件库源代码

test 单元测试用例

ts的配置项目不同配置也有差异没有普遍性,目前我们采用的eslint的extends如下

"extends": [
      "plugin:vue/essential",
      "@vue/standard",
      "@vue/typescript/recommended"
    ],


1.3 组件设计


组件库是有多个组件构成,每个文件夹是不同的组件,为了后边打包方便统一入口为index.ts

组件设计:

1. ui规范: 需要抽象和约定一套统一的视觉风格和交互规范。我们的ui规范基本和element-ui保持一致。

2. props设计: 组件库的 props 定义需要具备足够的可扩展性,而且组件内部状态基本依赖props传递,保持组件具有统一的输入

3. callback设计:组件想要暴露给业务代码的数据都是通过callbackback方式回传到父组件

我们以form-table为例

整体风格


筛选部分

暴露筛选前后的插槽,筛选部分通过,父组件通过props传递过来,组件本身通过不同的type实现不同的筛选逻辑



<slot name="before-button">
   <!-- 筛选条件之前的button -->
</slot>


 <div class="item" v-for="(item, i) in domFilter" :key="i">
        <span class="label" v-if="item.type !== 'button'">{{item.label}}:</span>
        <el-input
          v-if="item.type == 'input'"
          v-model.trim="pageFilter[item.name]"
          size="small"
          :placeholder="`请输入${item.label}`"
          clearable>
        </el-input>
        <el-select
          v-if="item.type == 'select'"
          v-model="pageFilter[item.name]"
          size="small"
          :placeholder="`请选择${item.label}`"
          filterable
          clearable>
          <div v-if="Object.prototype.toString.call(item.sugMap) === '[object Array]'">
            <el-option v-for="(v, i) in item.sugMap" :key="i" :label="v.label" :value="String(v.value)"></el-option>
          </div>
          <div v-if="Object.prototype.toString.call(item.sugMap) === '[object Object]'">
            <el-option  v-for="(v, k, i) in item.sugMap" :key="i" :label="v" :value="k"></el-option>
          </div>
        </el-select>
        <el-date-picker
          v-if="item.type == 'date'"
          v-model="pageFilter[item.name]"
          size="small"
          :placeholder="`请选择${item.label}`"
          type="daterange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          :pickerOptions="item.limit ? {
            onPick: pickDate => handlePick(pickDate, item.name),
            disabledDate: date => item.limit ? disabledDate(date, item.name, item.limit) : false
          } : ''"
        :editable="false">
        </el-date-picker>
        <el-date-picker
          v-if="item.type == 'datetime'"
          v-model="pageFilter[item.name]"
          size="small"
          :placeholder="`请选择${item.label}`"
          type="datetimerange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          :editable="false">
        </el-date-picker>
        <el-button v-if="item.type == 'button'" size="small" type="primary" @click="item.func">{{ item.label }}</el-button>
      </div>


<div class="item">
     <el-button size="small" type="primary" @click="queryList">查询</el-button>
     <!-- 查询之后的button -->
     <slot name="after-button" />
 </div>

table部分

样式,名称,prop通过父组件传递过来,所有的操作的逻辑父组件传递funtion实现


<el-table :data="tableData" border stripe v-loading="loading">
  <el-table-column
          v-for="(v, i) in tableLabel"
          :key="i"
          :label="v.label"
          :min-width="v.minWidth">
          <template slot-scope="scope">
            <span>{{ v.extends ? v.extends[scope.row[v.prop]]: scope.row[v.prop]}}</span>
          </template>
        </el-table-column>
        <el-table-column fixed="right" label="操作" v-if="Object.keys(tableOperation).length" :min-width="tableOperation.minWidth">
          <template slot-scope="scope">
            <span v-for="(item, index) in tableOperation.con" :key="index" class="operation">
              <el-button
                size="small"
                :type="item.type"
                @click="item.func(scope.row)"
                :disabled="item.disabled ? item.disabled(scope.row) : false">
                {{ item.label }}
              </el-button>
            </span>
          </template>
        </el-table-column>
      </el-table>


<section page v-if="total">
      <el-pagination
        @current-change="handleCurrentChange"
        :current-page="currentPage"
        :page-size="pageSize"
        layout="total, prev, pager, next"
        :total="total">
      </el-pagination>
    </section>


1.4 组件打包

1.如何打包成所需格式的组件,这种方式是没有办法做到按需加载的

target:打包方式 lib就是组件

name:组件名

dest:打包后的文件夹 打包入口文件

 vue-cli-service build --target lib --name sf-vue-ui --dest lib packages/index.ts

1.5 组件按需加载

babel-plugin-component,主要借助这个插件的能力

它自动将

import FormTable from 'vudui'

转换成

import FormTable from 'vudui/lib/form-table'

{
  "plugins": [
    [
      "component",
      {
        "libraryName": "vudui",
         "camel2Dash": false, // 是否把驼峰转换成xx-xx的写法
         "styleLibraryName": "theme"
      }
    ]
  ]
}


1.6 组件发布npm

npm adduser

npm login

npm publish

2.组件DEMO UI设计

demo 源码都在 examples 目录中维护, npm run dev启动

2.1 结构规划


2.2 ui开发

待续。。。

2.3 demo发布

将demo发布到gh-pages分支

  1. npm install gh-pages -D
  2. npm scripts: "deploy":"gh-pages -d dist"
  3. npm run deploy

3.组件测试

3.1测试框架选择

jest :facebook出,广泛使用在react,vue中,大而全继承了断言库,文档健全

mocha:清亮,所有的东西都需要自定义,文档少

3.2编写测试用例

vue测试jest走过的路,待续。。。

4.组件维护

PR 标题规则:[ bug fix / breaking change / new feature ] 组件名字:修改内容描述

  • 前面方括号用来区分 PR / issue 的类型:bug fix - 组件 bug 修复;breaking change - 不兼容的改动;new feature - 新功能
  • 修改内容尽可能言简意赅,总结 PR 的改动或者描述 issue
  • 描述请用中文
  • 组件名字请用英文,首字母大写

PR 用来生成 changelog,规范的 PR 有助于生成比较清晰的 changelog,一目了然

3.组件小tip:

3.1package.json配置:

main:代码入口。这个十分重要,特别是对于组件库。当你想在node_modules中修改你使用的某个组件库的代码时,首先在node_modules中找到这个组件库,第一眼就是要看这个main,找到组件库的入口文件。在这个入口文件中再去修改代码吧

repository:对于组件库很有用。让组件库使用者找到你的代码库地址。这个配置项会直接在组件库的npm首页生效

homepage: 项目主页。对于开发组件库来说挺有用的

keywords:关键词。一个字符串数组,对这个npm包的介绍。组件库必需,便于使用者在npm中搜索。对于公司业务项目,这个配置一般无所谓

desription:包的描述。开发组件库时必需,简明的向库的使用者介绍这个库是干嘛的。对于公司的业务项目,这个配置项一般无所谓

license:开源协议。对于开源组件库,这个十分重要。之前react还因为这事儿没少被社区嫌弃。开源协议略微复杂,用阮一峰前辈的一张图来说明一下吧。注:图里少了ISC, ISC和BSD差不多

3.2 readme徽章

shields.io/