关于Vue组件库开发详析


Posted in Javascript onJuly 01, 2018

前言

2017年是Vue.js大爆发的一年,React迎来了一个强有力的竞争对手,王者地位受到挑战(撰写此文时github上Vue与React的star数量已逼近)。我们团队这一年有十多个大型项目采用了Vue技术栈,在开发效率、页面性能、可维护性等方面都有不错的收效。 我们希望把这些项目中可复用的功能组件提取出来,给后续项目使用,以减少重复开发,提高效率,同时也为了致敬前端界“出一个框架,造一遍轮子”的行规, 一个基于Vue 2的移动端UI组件库被提上日程。

组件库的开发过程总的来说还是比较顺利的,这里与大家分享一些问题与思考。

脚手架选择

尽管我们团队的这些Vue技术栈项目的脚手架大都使用的是webpack,在为组件库选择脚手架的时候我们还是在webpack与Rollup中犹豫了一下。

Rollup看起来更适合组件库的开发,它把所有模块构建在一个函数内,执行效率更高,它支持Tree Shaking,只打包需要的代码,输出文件更小(webpack后来也支持了)。但综合考虑之后,我们还是选择了webpack作为打包工具。首先,按照规划,demo演示和文档页面也在这个脚手架中,所以对代码分割、热加载等功能是有需求的,而这方面能力Rollup远不及webpack。另外,这个组件库由多人开发维护,基于现有webpack脚手架开发成本更低、效率更高。选择webpack,让我们可以更专注于造轮子。

打包

即便选择了webpack作为打包工具,我们也并不希望这个库的使用场景局限在webpack项目中,通过AMD/CMD方式、甚至通过script标签直接引用等场景都应该得到支持。为了达到这个目的,我们需要在webpack配置文件中设置输出格式,需要配置的选项是output.libraryTarget,有以下可选值:

“var”(默认值)输出为一个变量

var MyLibrary = _entry_return_ ;

“this” 输出为this的一个属性

this["MyLibrary"] = _entry_return_ ;

“window” 输出为window对象的一个属性

window["MyLibrary"] = _entry_return_ ;

“global” 输出为global对象的一个属性

global["MyLibrary"] = _entry_return_ ;

“commonjs” 输出为exports 的一个属性

exports["MyLibrary"] = _entry_return_ ;

“commonjs2” 以module.exports形式输出

module.exports = _entry_return_ ;

“amd” 输出为AMD模块

“umd” 暴露给所有模块定义,允许它和CommonJS/AMD/全局变量一起工作

很显然,我们需要把output.libraryTarget的值设为“umd”,以使我们的库可以工作在各种场景下。

另一个与库打包有关的设置项是output.umdNamedDefine,在output.libraryTarget 设为umd,且output.library 也设置了的情况下,把此选项值设为true,将会为AMD模块命名。

webpackConfig.output = {
 path: path.resolve(__dirname, 'dist'),
 publicPath:"/",
 filename: '[name].js',
 library: 'xxx', //模块名称
 libraryTarget: 'umd', //输出格式
 umdNamedDefine: true //是否把模块名作为AMD输出的命名空间
};

Vue组件库只提供组件,Vue文件自身需要组件库使用者在项目中自行引入,库中无需打包。所以我们可以把Vue加到externals中。

externals: {
 vue: 'vue'
}

这样Vue就不会被打包。不过,有个问题,就是用script标签的形式引用Vue的时候,挂在window上的变量名是“Vue”,而不是我们需要的”vue”,因此使用时会报vue未定义的错误。

关于Vue组件库开发详析

还好,webpack的externals配置项支持传入一个对象,可以为不同导出形式指定不同名称。所以下面这种写法可以解决这个问题。

关于Vue组件库开发详析

组件类型

规划中的Vue组件库包含组件(Component)、指令(Directive)和过滤器(Filter)三种类型的存在。

比较特殊的是模态弹窗类(Modal)组件,如Dialog、Toast等等。页面中可能存在很多个Modal,而很多场景下用户的行为只会触发其中一部分,如果把所有可能弹出的Modal(特别是异步的、结构内容复杂的Modal)全部写在页面上,是否妥当?对于多页面应用,每个页面都写一遍或者再封装一层组件是否繁琐而冗余?这个问题在知乎上引发过讨论,尤大(Vue.js作者尤雨溪)本人在参与讨论时给出建议,组件多层嵌套时,应该把Modal放在根组件里,然后在子组件里通过事件触发。在具体应用里,应该这么用,这符合Vue提倡的“状态驱动”。不过在组件库里,我们还是希望提供一种更便捷更通用的方式来使用Modal类型的组件。

参考了Element UI等优秀组件库的做法,我们把Modal类型的组件挂到了Vue.prototype上,使之成为Vue的实例方法,一次安装、全局调用。

this.$dialog(options);

因此,我们的组件库组件类型还包括“实例方法”。

组件CSS作用域

对于一个组件,我们希望它的CSS只作用于当前组件内的元素,所以我们给每个组件的Vue单页面文件的style标签加上了scoped属性。编译后,HTML标签会被自动添加一个随机生成的唯一属性 (比如 data-v-f3f3eg9) ,同时对应的CSS选择器也会增加同名的属性选择器(如.example[data-v-f3f3eg9]),这样组件内的 CSS 便指定了作用域。

关于Vue组件库开发详析

编译后:

关于Vue组件库开发详析

通过scoped属性的确能达到给组件样式设置作用域的目的,基本能避免组件内的样式影响外部,但是它也带来了另外一个问题,就是给外部覆盖内部样式带来了不便。无论组件功能多么通用,接口多么灵活,只要涉及到UI,就难免无法满足所有项目样式需求,所以应该允许在具体的项目中根据需要覆盖组件部分甚至全部样式。而scoped随机生成属性名提高了覆盖样式的难度。

经过权衡,我们在组件里移除了scoped属性,改用class策略来避免组件内样式影响外部。当然,scoped属性也不是没有存在的意义,它更适合在具体应用中使用,对于复用性高的组件来说,不是最佳选择。

按需使用与自定义构建

随着项目推进,组件库里的组件越来越多,目前已超过40个,构建之后的文件也越来越大。如果某个应用只用到了库里的少数几个组件,完全没有必要使用完整的构建包,所以我们需要提供一种按需使用的方式。早期,我们是让用户通过私有npm安装组件库之后,根据应用自身需要直接引用src目录下组件源码的方式来实现按需加载。这种方式有较大局限性,因为引用的源码没有经过编译,需要用户自己去处理组件的依赖关系,ES6/SCSS/Vue模板等编译工作也需要用户在自己的项目里完成,繁琐、易出错,也难以支持webpack外的其他场景。 我们设想提供一种自定义构建的方式,来实现按需打包。首先让用户选择需要哪些组件,然后基于这些信息生成一个个性化的配置文件,再基于这个文件进行构建,最终只打包编译用户指定的这些组件。

那么,通过哪种方式与用户交互,收集用户指令呢?比较友好的方式是通过web,比如在项目主页中提供一个页面,让用户在线选择组件,然后下载构建之后的文件。而根据我们组件库目前的定位,推荐的使用方式是通过私有npm安装,所以我们首先推出的是通过命令行界面(CLI)方式来完成自定义构建。

用户只需要在终端执行命令“npm run custom”,即可得到全部组件的列表,通过键盘选择需要的组件,然后按下回车,脚手架便开始自动完成剩余的个性化构建工作。

关于Vue组件库开发详析

片刻之后,只包含用户所选组件的构建包会出现在dist目录下,文件体积比完整版本小很多。

关于Vue组件库开发详析

这种方式下,所选组件会经历组件库脚手架完整的构建流程,自动处理组件依赖关系,对ES6/SCSS/Vue等语法也进行了编译,构建出的文件也支持AMD/CMD/script标签直接引用等场景,能比较好的满足按需使用的需求。

图标

组件库UI组件难免会包含一些小图标,需要寻找一种合适的方式处理这些图标。

在应用开发中有时会把一些图片转成Base64编码放在代码里,这会使数据量增大30%左右,所以这种方式不适合较大图片。而对于小图标来说,增加的绝对数据量并不大,却能减少一个http请求,也不失为一种优化方案。不过,组件库较普通应用对数据量更为敏感,这种方式不是上策。

另一种处理小图标的经典方案是雪碧图(CSS Sprite),但这种基于精准位置信息的图标引用方式在移动端基于rem的布局中并不是那么受欢迎,因为rem布局自身就难以精确,如果用于组件库也会给按需引用带来一些不便。

对于小图标,在移动端更需要的是矢量方案,天然适配各种像素密度的屏幕。

在组件库中,比较流行的是采用基于CSS3字体(@font-face)的ICON FONT方案,也就是把图标放在一个自定义字体文件中。有很多优点,比如:

  • ICON在字体中是矢量存在,受移动端欢迎
  • 良好的浏览器兼容性,web字体并非CSS3发明,更早之前的浏览器(包括IE6)也都事实上支持,虽有些许差异,终归是有办法兼容的
  • 可通过CSS控制ICON颜色和透明度等样式,甚至可以实现颜色渐变效果

关于Vue组件库开发详析

我们并没有选择ICON FONT方案,我们认为SVG方案更适合移动端组件库:

  • SVG虽在PC端个别古董浏览器中兼容较差,但在移动端兼容良好
  • ICON FONT被认为是文本,所以一些浏览器会对其进行抗锯齿处理,这可能导致图标不那么锐利,清晰度打折扣
  • SVG样式控制比ICON FONT更灵活,甚至可以控制图标各个部分的颜色,实现彩色图标。而这对ICON FONT来说是不可能实现的
  • ICON FONT通常是用伪对象或伪类插入页面,其展示受到“line-height”、“vertical-align”、“letter-spacing”、“word-spacing”及字体相关CSS属性影响,也受到字体字符设计本身影响。而SVG在页面中就是一个标签,更方便控制,语义化也更好
  • 结合symbol元素可以实现所谓“SVG Sprite”,也就是把很多SVG图标整合在一起,通过ID引用指定图标,可以复用。这种方式比CSS Sprite还要方便,因为不需要关心图标具体位置信息

关于Vue组件库开发详析

SVG Sprite也不是必须手动去组合,借助webpack的 svg-sprite-loader 可以轻松实现SVG Sprite的动态生成,图标的按需加载不是梦。

扩展阅读

  • NutUI组件库 - https://nutui.jd.com
  • webpack output.libraryTarget - https://webpack.js.org/configuration/output/#output-librarytarget
  • umd - https://github.com/umdjs/umd

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JavaScript中的继承方式详解
Feb 11 Javascript
下一代Bootstrap的5个特点 超酷炫!
Jun 17 Javascript
Javascript中apply、call、bind的巧妙使用
Aug 18 Javascript
Node.js读写文件之批量替换图片的实现方法
Sep 07 Javascript
Ext JS 实现建议词模糊动态搜索功能
May 13 Javascript
JS中图片压缩的方法小结
Nov 14 Javascript
Vue利用canvas实现移动端手写板的方法
May 03 Javascript
Vue.js 实现微信公众号菜单编辑器功能(一)
May 08 Javascript
单页面vue引入百度统计的使用方法示例详解
Oct 13 Javascript
vue动态禁用控件绑定disable的例子
Oct 28 Javascript
构建Vue大型应用的10个最佳实践(小结)
Nov 07 Javascript
微信小程序实现下滑到底部自动翻页功能
Mar 07 Javascript
D3.js实现拓扑图的示例代码
Jun 30 #Javascript
详解angular如何调用HTML字符串的方法
Jun 30 #Javascript
angular6.0使用教程之父组件通过url传递id给子组件的方法
Jun 30 #Javascript
基于webpack4搭建的react项目框架的方法
Jun 30 #Javascript
AngularJs分页插件使用详解
Jun 30 #Javascript
ionic grid(栅格)九宫格制作详解
Jun 30 #Javascript
vue检测对象和数组的变化分析
Jun 30 #Javascript
You might like
php smarty 二级分类代码和模版循环例子
2011/06/01 PHP
PHP中去除换行解决办法小结(PHP_EOL)
2011/11/27 PHP
PHP常用文件操作函数和简单实例分析
2016/06/03 PHP
Yii2简单实现给表单添加验证码的方法
2016/07/18 PHP
ThinkPHP使用getlist方法实现数据搜索功能示例
2017/05/08 PHP
PHP创建XML的方法示例【基于DOMDocument类及SimpleXMLElement类】
2019/09/10 PHP
使用PHP开发留言板功能
2019/11/19 PHP
javascript事件模型代码
2007/07/01 Javascript
JAVASCRIPT IE 与 FF中兼容问题小结
2009/02/18 Javascript
解决IE下select标签innerHTML插入option的BUG(兼容IE,FF,Opera,Chrome,Safari)
2010/05/13 Javascript
关于JS字符串函数String.replace()
2013/04/07 Javascript
使用phantomjs进行网页抓取的实现代码
2014/09/29 Javascript
jquery.mobile 共同布局遇到的问题小结
2015/02/10 Javascript
jquery 正整数数字校验正则表达式
2017/01/10 Javascript
nodejs 终端打印进度条实例代码
2017/04/22 NodeJs
select自定义小三角样式代码(实用总结)
2017/08/18 Javascript
如何在js代码中消灭for循环实例详解
2018/07/29 Javascript
纯JS实现五子棋游戏
2020/05/28 Javascript
python 排列组合之itertools
2013/03/20 Python
python框架django基础指南
2016/09/08 Python
python PyTorch预训练示例
2018/02/11 Python
Python 脚本获取ES 存储容量的实例
2018/12/27 Python
python批量创建指定名称的文件夹
2019/03/21 Python
python将时分秒转换成秒的实例
2019/12/07 Python
使用OpenCV去除面积较小的连通域
2020/07/05 Python
小学生自我鉴定
2013/10/12 职场文书
护士节策划方案
2014/05/19 职场文书
社区先进事迹材料
2014/05/19 职场文书
销售员试用期自我评价
2014/09/15 职场文书
2014市府办领导班子“四风问题”对照检查材料思想汇报
2014/09/24 职场文书
2015年度党员个人总结
2015/02/14 职场文书
公务员保密工作承诺书
2015/05/04 职场文书
初婚初育证明范本
2015/06/18 职场文书
电台广播稿范文
2015/08/19 职场文书
深入详解JS函数的柯里化
2021/06/09 Javascript
源码安装apache脚本部署过程详解
2022/09/23 Servers