从0到1发布一个Vue Collapse组件

需求背景

最近在项目中遇到了一个相似Collapse的交互需求,所以到github上找了一圈关于Vue Collapse的相关轮子,可是多少都有些问题。有的是实现问题,例如vue2-collapse,伸缩部分采用max-height指定动画,存在缺陷;还有的是扩展性问题,遇到定制场景比较棘手。所以,决定本身撸一个Collapse组件。从项目中的一个需求,到目前已将它开源并发布到npm,仍是踩了许多坑的。代码虽然简单,可是过程却不太容易。所以这篇文章不是安利这款组件r-collapse-vue,仅仅是想记录一下整个开发生命周期,须要作什么,以及遇到什么问题。固然了,若是这个组件或是这篇文章对你有帮助,劳烦点进去给个star,万分感谢~javascript

开发流程

咱们的整个开发流程,能够简单的总结以下:html

  1. 项目脚手架搭建(Vue CLI3)
  2. 组件功能开发
  3. 单元测试(Vue Test Utils + Jest)
  4. 文档编写(Vue Styleguidist + Github Pages)
  5. 发布NPM
  6. 持续集成配置(TravisCI)

咱们来详细聊一聊每一个过程是如何实施的,且遇到了哪些问题。前端

脚手架搭建

脚手架咱们直接使用Vue CLI来搭建便可,其已经提供了丰富的功能,而且能够经过vue.config.js扩展webpack的能力。可是要注意的是,咱们的构建产物是一个模块,而不是咱们平时在项目中构建出一个应用。咱们但愿构建出来的模块是一个兼容CommonJs或是UMD,以便于使用者在不一样的环境中引用。所幸,Vue CLI3也给我提供了这样一个功能,详细可参考文档vue

其次,本次开发我选择了TypeScript,脚手架默认集成了vue-property-decorator。使用以后直观的感觉就是,Vue的整个生态对TS的支持还不够完善,但总体仍是比较爽的,期待官方在3.0中可以完全支持TS。本文主题不是讨论TS,所以简单罗列下使用时遇到的问题:java

  • 在template中没法作到智能提示,须要智能提示只能使用tsx,这一点是比较痛苦的
  • 定义Prop时须要加非空断言(!:),不然会报错,例如:
@Prop({ required: true })
public value!: String;
  • 使用Vue Test Utils写单测时,没法对自定义的Vue组件进行类型推导,见下文
  • 使用Vue Styleguidist编写文档demo不支持TS,见下文

组件功能开发

在平常写业务的时候,咱们可能会在组件当中耦合不少的业务逻辑。可是做为一个通用组件,咱们在开发的时候要尽量保证它的扩展性,所以咱们但愿达到的一个目标就是:在保证开发体验的前提下提升扩展性。对于Collapse组件,UI方面通常都是按照各自的设计稿来自行编写的,所以咱们只须要提供功能便可。更好的方式是提供默认的UI,但又能够支持彻底定制,这个是目前r-collapse-vue能够完善的一个点。
在进行功能设计的过程当中,咱们要先肯定咱们须要支持哪些功能,以r-collapse-vue举例,须要提供的功能包括:webpack

  • 基本的展开/收缩(支持动画)
  • 手风琴模式
  • 自定义点击事件
  • Collapse嵌套

在实现的过程当中,咱们也须要思考不少细节,举几个例子:git

  1. 使用者如何控制每个Collapse的状态?

最简单的想法是传递一个相似叫作status的prop,在每个Collapse内部去维护这个状态。可是这样会有一个问题,咱们如何去支持手风琴模式,即一个展开另外的都须要收起。按照这种作法,须要用一个父组件包裹,去获取每个Collapse子组件的实例,调用实例方法去控制。这样作不是不行,vue2-collapse就是这么作的,可是我认为不够优雅。所以咱们从新整理思路,每个Collapse之间的状态可能会互相影响,咱们经常使用的解决方法是状态提高,所以个人作法是抽象两个组件,Collapse和CollapsePanel,Collapse便是父组件,提供状态控制,将状态传递给其内部嵌套的CollapsePanel,在内部消化掉全部的逻辑,这更加符合单向数据流的思想,站在使用者角度来看,写法也可以相对统一,使用时咱们只需这么写:github

<r-collapse v-model="activeKeys">
    <r-collapse-panel name="a">xxxx</r-collapse-panel>
    <r-collapse-panel name="b">xxxx</r-collapse-panel>
</r-collapse>
  1. 实际场景中常常会对展开和收缩进行样式区分,如何帮助使用者提高开发体验?

见上面的代码,咱们在CollapsePanel中传入了一个name属性做为惟一标识,此时使用者可结合activeKeys自行判断当前panel是否展开:web

<r-collapse v-model="activeKeys">
    <r-collapse-panel
        name="a"
        :class="activeKeys.includes('a') ? 'active': ''"
    >
        xxxx
    </r-collapse-panel>
    <r-collapse-panel name="b">xxxx</r-collapse-panel>
</r-collapse>

这种方法虽然能够,可是存在两个问题:vue-cli

  • 用户须要自行添加逻辑,体验不够友好
  • 每次从新渲染都会执行额外的逻辑判断,性能不够友好

所以,能够提供一个activeClass的prop,让使用者能够自定义展开状态的类名,就能够避免以上的问题。

这些细节问题看似简单,可是做为一个通用组件的开发者,咱们应该常常站在使用者的角度看问题,才能不断地提高组件的开发体验。

单元测试

一个优秀的开源组件必定少不了单元测试,例如Ant Design等开源库都有着很高的单测覆盖率。一开始写单测可能会以为耗时、没有必要,但其实单测可以带来诸多的好处:

  1. 单测相较手动测试,可以减小bug率,覆盖的场景更全,且测试较为方便
  2. 开源的组件可能会有不少的维护者,单测可以下降模块之间互相影响产生bug的几率
  3. 使用者通常都会选择单测覆盖率较高的轮子

所以,单测必不可少,目前前端常见的选择包括:

  • Jest,FaceBook出品,配置简单,使用JSDOM模拟测试环境,当遇到操做真实DOM的场景,如获取scrollHeight等比较乏力
  • Karma + Mocha,Mocha同Jest都是测试框架,而Karma为框架提供了真实的浏览器测试环境,若是代码中对DOM操做较多,建议使用这种组合。可是Mocha配置较复杂,且须要自行安装断言库

Vue当中已经给咱们提供了单测相关的工具Vue Test Utils,它提供了不少功能,如组件挂载,获取实例等等,使用它配合Jest或者Mocha可以比较方便的完成单测,详情参考文档

在编写单测时,咱们须要注意,对于UI组件来讲,不该一味追求行级覆盖率,应当只关注输入输出,避免涉及过多的实现细节,从而避免琐碎的测试。例如,咱们测试展开功能,只须要触发click,检测status是否为true便可,无需关注过程当中是触发了xxx事件仍是发生了其余事情,这样当咱们的逻辑修改后可以保证单测还能有效。同时,在用TS编写单测时,经过Vue Test Utils建立的wrapper是普通的Vue类型,所以自定义的Vue组件没法进行类型推导,此时要获取实例属性时须要经过(wrapper.vm as any).xxx来获取。经查阅资料,官方表示目前无法解决这个问题,只能使用这种方式。

文档编写

一个好的文档可以方便使用者明白你的设计理念,所以咱们想要的文档不只须要有完整的API描述,而且在展现demo时可以同时展现源码,相似于在Ant Design或Element中那样。咱们这边使用的是Vue Styleguidist

它经过vue-docgen-api,可以将注释转换成属性描述展示在页面上。所以咱们只须要写注释,就可以生成组件属性相关的文档。而咱们的另外一个需求,在展现demo时可以同时展现源码,它也可以作到。咱们能够经过两种方式:

  • 在Vue组件中使用<docs></docs>标签来写demo,这样作对组件有侵入,感受不太好
  • 新建一个markdown文件,内部经过特殊的标记写入vue代码便可

咱们选用第二种方式,可是又遇到了许多坑。好比写入md的Vue代码不支持TS,试了不少的方法都没有解决,后来仍是改为了JS写法;还有SCSS使用嵌套时,嵌套的内容未被正确解析,后改为了CSS。其实这个东西的实现难度并不高,在md中写Vue无非就是写个webpack插件解析.md格式的文件,取出Vue的部分经过vue-loader处理,鉴于bug这么多且样式我认为不够美观,以后有时间能够再造个轮子玩一玩。

在Vue CLI3中使用Vue Styleguidist十分方便,只要运行:

vue add styleguidist

而后在package.json的scripts中添加:

"serve:doc": "vue-cli-service styleguidist",
"build:doc": "vue-cli-service styleguidist:build"

就能够拆箱即用了。

文档编写完成,咱们执行yarn build:doc构建文档,发现输出的是一个html文件,此时咱们能够选择使用Github Pages来做为咱们的静态资源服务器展现文档,由于它方便部署且免费。过程以下:

  1. 将styleguide.config.js中的styleguideDir选项改成"docs",即将build的目标目录设置为docs
  2. 在Github对应仓库的settings中将GitHub Pages的Source选项设置为master branch/docs folder,意味着会自动从仓库的docs目录获取静态资源

这样每次更新docs会自动部署更新文档,相似于这样https://danceonbeat.github.io/r-collapse-vue/,惟一的缺点就是国内打开有点慢。

说完文档,咱们还须要编写在Github上展现的README,这里推荐一个生成README的库,readme-md-generator,格式很是简洁且美观。在README中,咱们能够添加以下的小图标:

这个可使用shields生成,它能关联你的NPM、Github等等,实时更新icon信息,有了它文档逼格瞬间高多了。

发布NPM

要将包发布到NPM,咱们须要作以下的准备工做:

  1. https://www.npmjs.com/上注册一个NPM的帐号
  2. 本地执行
npm login --registry=https://registry.npmjs.org

注意,这边加上registry为了防止在全局或当前环境覆写.npmrc,致使登陆的不是NPM源。

  1. 修改package.json的配置,能够参考v-collapse-vue的部分配置:
{
  "name": "r-collapse-vue",
  "version": "1.0.0",
  "description": "a collapse component for VueJs",
  "author": {
    "name": "Ray",
    "email": "zhurui0904@gmail.com"
  },
  "main": "dist/r-collapse-vue.common.js",
  "files": [
    "dist"
  ],
  "keywords": [
    "Vue",
    "collapse"
  ],
  "publishConfig": {
    "registry": "https://registry.npmjs.org"
  },
  "repository": {
    "type": "git",
    "url": "git@github.com:DanceOnBeat/r-collapse-vue.git"
  }
}
  1. 最后执行npm publish便可完成发布

每次发布新版本以前,咱们能够经过

npm version major/minor/patch -m 'xxx'

来修改版本号而且打上tag,此tag非NPM的dist-tag,而是Git的tag。一个版本对应一个tag,并经过

git push origin master --tags

将tag也推到远程仓库,这样在仓库中咱们就能清楚地看到发布的记录,方便往后回滚之类的操做。具体的版本规则能够参考semver规范

持续集成(CI)

当开发结束后,咱们须要跑测试,测试经过后,还须要构建生成dist目录,最后发布到NPM。每次修改都作这样一套操做实在繁琐,而且容易遗漏步骤,这时候咱们就须要使用CI将咱们的流程自动化,我在这边选择了TravisCI。同时,咱们还能够经过Codecov,将咱们的单测报告上传至Codecov服务器,这样就能同步更新Codecov的icon。

在配置CI时,我本来将生成docs的步骤也添加了进去,此时咱们在deploy中会有两个步骤,以下:

deploy:
  - provider: npm
    email: zhurui0904@gmail.com
    api_key: $AUTH_TOKEN
    on:
      tags: true
      branch: master
    skip_cleanup: true
  - provider: pages
    skip_cleanup: true
    github_token: $GITHUB_TOKEN
    keep_history: true
    target_branch: master
    on:
      branch: master

这会形成一个问题是provider: pages会将CI服务器生成的新的docs目录push到咱们的Github仓库,这又会触发一次CI,以致于无限循环。后来也没找到合适的解决方案,又考虑到文档不常常更新,就将文档部署相关的部分从CI中移除了。若是你们有合适的解决方案,能够留言告诉我一下,不胜感激。

以前咱们提到一个NPM发布版本对应一个tag,所以咱们能够在配置中添加

if: tag IS present

限定只在提交了tag才触发一次自动化构建,这样基本上就大功告成了。

总结

这是一次很是有趣的造轮体验,代码虽然不难,可是过程当中又学习到了不少新的东西,包括单元测试、文档编写等等,但愿这篇文章能给准备造轮或想要造轮的小伙伴提供一点帮助。

相关文章
相关标签/搜索