关于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 相关文章推荐
如何在标题栏显示框架内页面的标题
Feb 03 Javascript
用javascript父窗口控制只弹出一个子窗口
Apr 10 Javascript
js中eval()函数和trim()去掉字符串左右空格应用
Feb 02 Javascript
JavaScript常用脚本汇总(一)
Mar 04 Javascript
jQuery获取table行数并输出单元格内容的实现方法
Jun 30 Javascript
javascript比较语义化版本号的实现代码
Sep 09 Javascript
JS自定义函数对web前端上传的文件进行类型大小判断
Oct 19 Javascript
JavaScript之class继承_动力节点Java学院整理
Jul 03 Javascript
javascript高级模块化require.js的具体使用方法
Oct 31 Javascript
浅谈在react中如何实现扫码枪输入
Jul 04 Javascript
在layui中layer弹出层点击事件无效的解决方法
Sep 05 Javascript
vue 图片裁剪上传组件的实现
Nov 12 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时的知识积累总结
2013/06/07 PHP
php中使用GD库做验证码
2016/03/31 PHP
PHP图像处理 imagestring添加图片水印与文字水印操作示例
2020/02/06 PHP
Js日期选择自动填充到输入框(界面漂亮兼容火狐)
2013/08/02 Javascript
使用Jquery实现点击文字后变成文本框且可修改
2013/09/21 Javascript
详解Javascript事件驱动编程
2016/01/03 Javascript
jquery ajax双击div可直接修改div中的内容
2016/03/04 Javascript
jQuery插件Easyui设置datagrid的pageNumber导致两次请求问题的解决方法
2016/08/06 Javascript
bootstrap日历插件datetimepicker使用方法
2016/12/14 Javascript
原生JS下拉加载插件分享
2016/12/26 Javascript
ES6新特性之Object的变化分析
2017/03/31 Javascript
使用ionic(选项卡栏tab) icon(图标) ionic上拉菜单(ActionSheet) 实现通讯录界面切换实例代码
2017/10/20 Javascript
Vue ElementUi同时校验多个表单(巧用new promise)
2018/06/06 Javascript
layui 图片上传+表单提交+ Spring MVC的实例
2019/09/21 Javascript
[05:13]2018DOTA2亚洲邀请赛主赛事第二日战况回顾 LGD、VG双雄携手晋级
2018/04/05 DOTA
Python版微信红包分配算法
2015/05/04 Python
Python实现App自动签到领取积分功能
2018/09/29 Python
python操作小程序云数据库实现简单的增删改查功能
2019/06/06 Python
python 搜索大文件的实例代码
2019/07/08 Python
python支付宝支付示例详解
2019/08/22 Python
python实现密度聚类(模板代码+sklearn代码)
2020/04/27 Python
Python API 操作Hadoop hdfs详解
2020/06/06 Python
Python word文本自动化操作实现方法解析
2020/11/05 Python
详解HTML5中的Communication API基本使用方法
2016/01/29 HTML / CSS
韩国三大免税店之一:THE GRAND 中文免税店
2016/07/21 全球购物
请解释在new与override的区别
2012/10/29 面试题
电子信息专业学生自荐信
2013/11/09 职场文书
年会搞笑主持词串词
2014/03/24 职场文书
清明节网上祭英烈活动总结
2014/04/30 职场文书
植树节标语
2014/06/27 职场文书
国家助学贷款承诺书
2015/04/30 职场文书
通用员工手册范本
2015/05/14 职场文书
理想国读书笔记
2015/06/25 职场文书
教师节主题班会教案
2015/08/17 职场文书
开发一个封装iframe的vue组件
2021/03/29 Vue.js
MyBatis 动态SQL全面详解
2021/10/05 MySQL