关于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技术技巧大全(五)
Jan 22 Javascript
游览器中javascript的执行过程(图文)
May 20 Javascript
基于JavaScript实现 获取鼠标点击位置坐标的方法
Apr 12 Javascript
随窗体滑动的小插件sticky源码
Jun 21 Javascript
JS操作JSON要领详细总结
Aug 25 Javascript
jquery等宽输出文字插件使用介绍
Sep 18 Javascript
jQuery()方法的第二个参数详解
Apr 29 Javascript
轻松学习jQuery插件EasyUI EasyUI表单验证
Dec 01 Javascript
详解Vue.use自定义自己的全局组件
Jun 14 Javascript
vue计算属性及使用详解
Apr 02 Javascript
vue 解决addRoutes动态添加路由后刷新失效问题
Jul 02 Javascript
JavaScript JSON使用原理及注意事项
Jul 30 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
重置版宣传动画
2020/04/09 魔兽争霸
PHP中,文件上传
2006/12/06 PHP
php压缩多个CSS为一个css的代码并缓存
2011/04/21 PHP
php中全局变量global的使用演示代码
2011/05/18 PHP
php截取html字符串及自动补全html标签的方法
2015/01/15 PHP
在PHP中使用FastCGI解析漏洞及修复方案
2015/11/10 PHP
让你的网站可编辑的实现js代码
2009/10/19 Javascript
javascript 获取所有id中包含某关键字的控件的实现代码
2010/11/25 Javascript
使用mouse事件实现简单的鼠标经过特效
2015/01/30 Javascript
jQuery获得包含margin的outerWidth和outerHeight的方法
2015/03/25 Javascript
JavaScript去除数组里重复值的方法
2015/07/13 Javascript
javascript设计简单的秒表计时器
2020/09/05 Javascript
完美解决js传递参数中加号和&号自动改变的方法
2016/10/11 Javascript
BootstrapTable请求数据时设置超时(timeout)的方法
2017/01/22 Javascript
JavaScript中for循环的几种写法与效率总结
2017/02/03 Javascript
vue 2.0封装model组件的方法
2017/08/03 Javascript
jquery select插件异步实时搜索实例代码
2017/10/20 jQuery
Node.js 使用request模块下载文件的实例
2018/09/05 Javascript
深入了解JavaScript词法作用域
2020/07/29 Javascript
如何在 Vue 中使用 JSX
2021/02/14 Vue.js
python得到单词模式的示例
2018/10/15 Python
对python3新增的byte类型详解
2018/12/04 Python
python字典嵌套字典的情况下找到某个key的value详解
2019/07/10 Python
Django+Uwsgi+Nginx如何实现生产环境部署
2020/07/31 Python
用CSS3实现瀑布流布局的示例代码
2017/11/10 HTML / CSS
英国鹦鹉店:Parrot Essentials
2018/12/03 全球购物
个性化皮包、小袋、生活配件:Mon Purse
2019/03/26 全球购物
中国包裹转运寄送国际服务:Famiboat
2019/07/24 全球购物
Chain Reaction Cycles俄罗斯:世界上最大的在线自行车商店
2019/08/27 全球购物
教师应聘个人求职信
2013/12/10 职场文书
七一党建活动方案
2014/01/28 职场文书
3.15国际消费者权益日主题活动活动总结
2014/03/16 职场文书
教师节倡议书
2014/08/30 职场文书
个人党性分析材料
2014/12/19 职场文书
2015年节能降耗工作总结
2015/05/22 职场文书
2016年学校党支部创先争优活动总结
2016/04/05 职场文书