Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cube-ui技术内幕 #31

Open
dolymood opened this issue Apr 8, 2018 · 1 comment
Open

cube-ui技术内幕 #31

dolymood opened this issue Apr 8, 2018 · 1 comment

Comments

@dolymood
Copy link
Member

dolymood commented Apr 8, 2018

cube-ui 是滴滴去年底开源的一款基于 Vue.js 2.0 的移动端组件库,主要核心目标是做到体验极致、灵活性强、易扩展以及提供良好的周边生态—后编译

自 17 年 11 月开源至今已有 5 个月,在这个过程中 cube-ui 受到了不少的关注,同时从社区中也收到了很多很好的反馈和建议。我们也一直在迭代更新,从最初的 1.0 版本到最近发布的 1.7 的版本,除了对原有组件做一些增强优化,我们也提供了很多新的组件。此外,周边后编译技术生态也做了很多优化,满足于更多场景需求,官网也做了一次升级。

接下来就重点介绍下 cube-ui 在这个过程中的有哪些成果以及一些设计细节。

关键成果

cube-ui 的组件数已经从最初的 14 个增长了 28 个,足足翻了一倍,已有的组件生态:

cube-ui components

扫码体验:

cube-ui example

除了上述的组件外,cube-ui 还对外暴露了三个模块:

而且 cube-ui 也已经支持了如下特性:

此外,cube-ui 的周边生态也有了进一步丰富:

设计细节

针对于上边所介绍的关键成果,我们来聊聊具体设计上的细节。

组件模块

  • 滚动 & Picker 类组件

    在移动端,由于手机尺寸以及交互特性,我们需要处理很多滚动类需求:下拉刷新、上拉更多、轮播等以及 Picker 选择等场景。cube-ui 底层滚动类组件以及 Picker 类依赖于我们团队的移动端利器 better-scroll 实现,基于其出色的体验进而保证了我们上层封装的滚动类、Picker 类组件的出色的交互体验。

  • 弹出层类组件

    在实际开发中我们会遇到很多弹出层类组件,因为我们设计了一个基础弹出层组件 Popup,它主要解决移动端最为常见的居中(Tip:文本换行位置也很重要哦)、置底以及是否有蒙层效果,借助于它来实现绝大多数的弹出层组件。

    另一个常见的痛点就是由于弹出类组件往往是全屏的状态,如果我们按照 Vue 推荐的声明式的语法在子组件里使用弹出层组件,由于嵌套层级问题,很容易受到父级元素的样式影响。为此我们单独开发了 create-api 模块,通过 API 的形式将实例化的弹出层组件动态挂载到 Body 元素下,因此摆脱了父级元素样式的影响,同时会随着使用它的组件的销毁而自动销毁,且为了降低开销成本,根据需要有些弹出层类组件都被设计成了单例模式。它是一个很通用的能力,我们把这样的一个便捷的 API 对外暴露出去,开发者也可以根据实际场景将自己开发的组件通过 createAPI 进行注册,进而也可解决上述痛点。

  • 表单类组件

    表单类需求往往特应性比较大,交互也很难做到统一,但是仍然可以有主流的表单设计交互,在 cube-ui 中表单可以设置 layout 来决定样式甚至是交互,满足日常场景需求。在表单设计中有两个很重要的组件:ValidatorForm。Validator 成为独立的组件主要基于校验场景不确定性,同时还需要满足各种形式的校验,所以 Validator 就只做了两件核心的事情,对数据源进行校验以及对应的错误信息的展示。考虑到开发者开发表单的便利性,我们参考 vue-form-generator 的设计,把表单设计成了根据 Schema 配置自动生成表单,这样开发表单的成本就降低了很多;同时为了兼顾灵活性,也支持通过插槽来自定义开发者需要的结构交互。

后编译

后编译是 cube-ui 的一个重要的生态,借助于后编译,整个的 web 应用的开发都可以直接基于 ES2015+ 进行开发,而项目依赖的一些 NPM 包也是可以直接使用 ES2015+ 进行开发,并且无需编译可直接发布到 NPM 平台上(也可以是自己 NPM 私服)。这样,这些组件库或者工具就可以有更多的想象空间、可以做更多有意思的事情。

cube-ui 支持的两个特性自定义主题以及 rem 布局都是基于我们主推的后编译技术实现。

接下来一起来看下这两个特性实现的细节。

自定义主题

一般而言,组件库都是有默认主题的,而往往还会搭配有多套主题(PC 类组件库比较常见)。现在借助于 CSS 预处理器,我们可以给组件定义一些变量(一般都是颜色值),然后在组件对应的样式中使用。

对于自定义主题这种需求,主流的做法有:样式覆盖和修改变量。

  1. 样式覆盖

    样式覆盖是最古老的做法,但是缺点也很明显,第一就是样式冗余问题,默认主题样式是一直存在的;第二就是开发者需要确切的知道样式对应的优先级去覆盖,要么是同级的优先级样式后置,要么就是提升自身覆盖的样式优先级。

    当然,样式覆盖的做法也是有优点的,那就是对于多主题同时存在,自由切换场景会比较合适。

  2. 修改变量

    现在有很多的 CSS 预处理器可以选择,每一种 CSS 预处理器都提供了变量功能,借助于变量,我们可以很容易创建一个主题文件,里边包含组件依赖的变量定义。要实现自定义主题,开发者需要在自己项目下创建一个单独的样式文件,定义赋值变量,同时引入组件库自身源码下的主题文件。

    本质上也是一种后编译做法,这个编译是利用 CSS 预处理器自身的变量能力达到目的。对于 Vue 组件库而言,主流的也是推荐的做法是把样式写在 .vue 文件中,这样便于维护,比较符合组件化开发思维;但是为了方便的使用预处理器实现自定义主题,通常都会把样式单独拿出来,一般的做法是创建一个样式文件夹,里边包含所有组件样式,而在 .vue 文件中则是没有样式的。

  3. cube-ui 做法

    核心点就是借助于后编译,我们可以按照原有我们习惯的方式去书写组件,即在 .vue 文件中包含模板、脚本和样式。如果需要自定义主题,就在自己项目下创建一个主题文件,里边定义变量,这个做法和一般的修改变量做法一样,但是不需要引入所有样式入口文件,因为也不存在这样的一个文件;同时借助于 webpack,我们完全可以做到在不侵入源码的情况下,做到主题定制。

接下来就看下具体做法,如果是新创建的项目,那么推荐使用 Vue-cli + cube-template 模板生成;而如果是现成的项目,则具体参考官方文档 - 主题 中配置。主要有两个核心点:

  • 创建主题文件 theme.styl,一般放在 src/ 目录下
  • 修改 webpack 中关于 stylus-loader 的配置项:添加 import 字段用于依赖自定义主题文件

接下来就看一个简单项目演示,假设创建了一个 demo 的项目,这个项目默认跑起来是这样的:

如果我们想要把项目中使用的按钮的背景色该换掉,那么可以修改 theme.styl 的文件内容:

// 如果你需要使用 cube-ui 自带的颜色值 需要 require 进来
@require "~cube-ui/src/common/stylus/var/color.styl"

// button
$btn-bgc := #409eff
$btn-bdc := #409eff
$btn-active-bgc := #66b1ff
$btn-active-bdc := #66b1ff

配合我们的 webpack 配置,刷新后的样子为:

这样我们就可以轻松做自己想要的主题定制,所要做的就是修改 cube-ui 已经定义好的变量值即可。对于 cube-ui 组件库自身,则不会有任何修改,且对于应用开发者而言,用不用自定义主题,本身的源代码不用修改,只需要创建一个主题文件(无需手工引入)配合 webpack 插件配置即可。

其实对于主题定制,还可以更进一步,未来 cube-ui 会考虑借助于 CSS 自身支持的变量(自定义属性)达到主题定制的目的,例如可以把处理器变量改为原生的变量,编译的话可以通过 post-css-variables 插件把默认变量值做替换,可以实现和现有编译后功能相同的效果,同时在后编译的情况下不失原生 CSS 变量的动态优势。这样,不仅可以做到主题定制,也可以做到多主题的自由切换,因为 CSS 原生变量可以直接修改变量值而不需要通过事先写死然后切换 class 覆盖的方式做多主题切换。

rem 布局

在移动端还是有很多设计师、产品或者开发者偏爱用缩放来达到不同尺寸屏幕适配目的,而缩放的实现一般都是采用 rem 进行布局,业内比较出名的方案就是手机淘宝前端团队开源的 lib-flexible

现在其实是不推荐使用 rem 进行布局的,如果真的要缩放的效果,可以考虑 vw vh 等 CSS 单位来实现。

rem 布局有两个核心的点:

  1. 在运行时动态根据视口宽度更新 rem 的值,即修改根元素 HTML 的 font-size 的值
  2. 在编译时(或开发时)需将设计稿的 px 单位转换为 rem 单位

对于组件库而言,如果想要同时做到即支持普适的 px 又支持 rem 这种方式的话,社区貌似还没见到。和后编译搭配,则比较容易实现,在 cube-ui 中,已经提供了 rem 支持,主要采取的方案:

  1. 可选的 amfe-flexible, 也就是 lib-flexible 动态计算更新 rem 的值(注 2.x 版本)
  2. 选择了 postcss 的插件 postcss-px2rem 作为将 px 转换为 rem 的库

这其实是对组件库本身有了一定要求,和尺寸相关的尽量要用样式控制,这样才能通过处理工具 postcss-px2rem 将 px 单位处理成 rem 单位,进而实现动态缩放需求。

来看下 cube-ui 使用 rem 的效果,默认 iPhone 5 尺寸效果:

当尺寸变大,例如为 iPhone 6 Plus 尺寸时效果:

可以看出整体的效果,当尺寸较小时,Button 和 Toast 都是比较小的,而当尺寸比较大时,相对应的都会更大,达到了缩放的目的。

上层扩展

这里上层扩展主要是指基于组件库进行二次封装,例如在滴滴内部,我们的很多业务组件库就是在开源的 cube-ui 组件库之上做增强而来的。

这个能力是非常重要的,因为移动端组件库和 PC 组件库最大的区别是移动端多是 to C 的业务场景,不同的业务场景下的设计是不一样的,所以 cube-ui 专注于通用组件和基础能力的建设,并不会在布局和业务组件方面大做文章;而 PC 组件库一般都是用于 to B 的场景,如内部 MIS 类的系统,对于设计的要求并没有特别苛刻,所以基础的样式,组件都是可以统一的。因此 cube-ui 的定位并不是要提供一个“大而全”的组件库,而是提供了二次扩展的能力,目标是任何移动端的业务场景都可以基于 cube-ui 提供的能力做二次扩展。

以我们的快速上手教程为例,我们要开发如下图的弹窗组件。

我们基于 cube-ui 提供的能力开发它就非常方便了。首先可以基于 Popup 组件开发一个 subscribe-dialog.vue 组件:

<template>
  <div class="subscribe-dialog-view">
    <cube-popup ref="popup" @mask-click="hide">
      <div class="subscribe-dialog-wrapper">
        <span class="close" @click="hide"><i class="cubeic-close"></i></span>
        <div class="title">开启推送通知</div>
        <img src="./subscribe.png">
        <p class="desc">第一时间获取最新鲜出炉的新闻攻略、赛事咨询、数据专题、精彩视频</p>
        <cube-button class="button" @click="start">现在开启</cube-button>
      </div>
    </cube-popup>
  </div>
</template>

<script>
export default {
  name: 'subscribe-dialog',
  methods: {
    show () {
      this.$refs.popup.show()
      this.$emit('show')
    },
    hide () {
      this.$refs.popup.hide()
      this.$emit('hide')
    },
    // ...
  }
}
</script>

接着使用 createAPI 模块把它变成一个 API 式的组件:

import SubscribeDialog from './components/subscribe-dialog/subscribe-dialog'
createAPI(Vue, SubscribeDialog, [], true)

然后调用它就非常方便了:

this.subscribeDialog = this.$createSubscribeDialog()
this.subscribeDialog.show()

周边生态

周边生态有两个核心:后编译 + 按需引入。为此,我们开发了两个 webpack 的插件来帮助我们更好的去使用、开发。

webpack-post-compile-plugin

这个插件主要是读取应用 package.json 中的 compileDependencies 字段的值(用于指定应用需要后编译哪些依赖包),而且还能解决嵌套后编译包的问题,因为开发者只需要关注自己依赖需要后编译的包,而不需要关注依赖的依赖包,这样就能构成一条生态链。

为什么不是一个 NPM 包自己声明需不需要后编译,而是由使用者去声明?

主要考虑整个 NPM 生态,例如 lodash-es 并不在我们控制范围之内,为了更好的使用整个 NPM 生态圈的包,我们决定由使用者去声明需要后编译的 NPM 包。

webpack-transform-modules-plugin

这个插件主要解决更方便、友好地使用按需引入的问题,为了更好的统一应用使用后编译和不使用的情况,我们在原本 babel-transform-imports 的基础上做了升级优化产出了 babel-plugin-transform-modules 插件,但是和后编译的场景类似,这个是不能解决后编译场景下 NPM 包嵌套按需引入的问题的,为此才开发了 webpack-transform-modules-plugin 这个插件,和 compileDependencies 字段类似,我们新增了 transformModules 字段来声明按需引入的 NPM 包的的转换规则,例如:

"transformModules": {
  "cube-ui": {
    "transform": "cube-ui/src/modules/${member}",
    "kebabCase": true
  }
}

当然在后编译的场景下,我们借助于 webpack 4 Tree shaking 中新增的 side-effects 也可以达到目的,这个是未来我们去优化的方向。

脚手架 & 教程

任何的技术都是有成本的,我们新增了 webpack 插件,也有一些需要配合的改动,所以为了降低开发者成本,我们提供了适用于 vue-cli 脚手架的模板 cube-template,当然对应的也会新增一些配置项,感兴趣的可以了解下cube-template wiki

同时为了初次使用 cube-ui 的开发者快速上手,我们还有一个简单的上手教程 cube-application-guide

展望

cube-ui 目前还处于初步的阶段,还缺少很多组件,但是我们一直在努力,希望在很快的未来可以提供更多更好用的组件。不仅如此,我们希望的是除去组件库本身,额外还会丰富周边的整个生态建设,给开发者一个良好的生态环境,进一步提升开发体验,提升应用性能等。当然,我们也希望社区的小伙伴也能参与进来,一块共同建设,共同进步。

未来 cube-ui 会朝着如下方面继续前行:

  • 丰富组件
  • 组件优化
  • 文档优化
  • 示例优化
  • 周边建设

希望感兴趣的同学可以一起共建或者加入我们团队,一起玩技术!

@iysf
Copy link

iysf commented Dec 5, 2018

我觉得非常nice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants