ElementUI 源码简析——源码结构篇
ElementUI 作为当前运用的最广的 Vue PC 端组件库,很多 Vue 组件库的架构都是参照 ElementUI 做的。作为一个有梦想的前端(咸鱼),当然需要好好学习一番这套比较成熟的架构。
目录结构解析
首先,我们先来看看 ElementUI 的目录结构,总体来说,ElementUI 的目录结构与 vue-cli2
相差不大:
- .github:存放贡献指南以及 issue、PR 模板,这些是一个成熟的开源项目必须具备的。
- build:毫无疑问,看文件夹名称就知道是存放打包工具的配置文件。
- examples:存放 ElementUI 组件示例。
- packages:存放组件源码,也是之后源码分析的主要目标。
- src:存放入口文件以及各种辅助文件。
- test:存放单元测试文件,合格的单元测试也是一个成熟的开源项目必备的。
- types:存放声明文件,方便引入 typescript 写的项目中,需要在
package.json
中指定 typing 字段的值为 声明的入口文件才能生效。
说完了文件夹目录,抛开那些常见的 .babelrc
、.eslintc
等文件,我们来看看根目录下的几个看起来比较奇怪的文件:
- .travis.yml:持续集成(CI)的配置文件,它的作用就是在代码提交时,根据该文件执行对应脚本,成熟的开源项目必备之一。
- CHANGELOG:更新日志,土豪的 ElementUI 准备了 4 个不同语言版本的更新日志。
- components.json:配置文件,标注了组件的文件路径,方便 webpack 打包时获取组件的文件路径。
- element_logo.svg:ElementUI 的图标,使用了 svg 格式,合理使用 svg 文件,可以大大减少图片大小。
- FAQ.md:ElementUI 开发者对常见问题的解答。
- LICENSE:开源许可证,ElementUI 使用的是 MIT 协议,使用 ElementUI 进行二次开发的开发者建议注意该文件。
- Makefile:在 .github 文件夹下的贡献指南中提到过,组件开发规范中的第一条:通过
make new
创建组件目录结构,包含测试代码、入口文件、文档。其中make new
就是make
命令中的一种。make
命令是一个工程化编译工具,而 Makefile 定义了一系列的规则来制定文件变异操作,常常使用 Linux 的同学应该不会对 Makefile 感到陌生。
入口文件解析
接下来,我们来看看项目的入口文件。正如前面所说的,入口文件就是 src/index.js
:
/* Automatically generated by './build/bin/build-entry.js' */
import Pagination from '../packages/pagination/index.js';
// ...
// 引入组件
const components = [
Pagination,
Dialog,
// ...
// 组件名称
];
const install = function(Vue, opts = {}) {
// 国际化配置
locale.use(opts.locale);
locale.i18n(opts.i18n);
// 批量全局注册组件
components.forEach(component => {
Vue.component(component.name, component);
});
// 全局注册指令
Vue.use(InfiniteScroll);
Vue.use(Loading.directive);
// 全局设置尺寸
Vue.prototype.$ELEMENT = {
size: opts.size || '',
zIndex: opts.zIndex || 2000
};
// 在 Vue 原型上挂载方法
Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
Vue.prototype.$message = Message;
};
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
version: '2.9.1',
locale: locale.use,
i18n: locale.i18n,
install,
CollapseTransition,
// 导出组件
};
总体来说,入口文件十分简单易懂。由于使用 Vue.use
方法调用插件时,会自动调用 install
函数,所以只需要在 install
函数中批量全局注册各种指令、组件,挂载全局方法即可。
ElementUI 的入口文件有两点十分值得我们学习:
- 初始化时,提供选项用于配置全局属性,大大方便了组件的使用,具体的可以参考我之前的那篇文章。
- 自动化生成入口文件
自动化生成入口文件
下面我们来聊聊自动化生成入口文件,在此之前,有几位同学发现了入口文件是自动化生成的?说来羞愧,我也是在写这篇文章的时候才发现入口文件是自动化生成的。
我们先来看看入口文件的第一句话:
/* Automatically generated by './build/bin/build-entry.js' */
这句话告诉我们,该文件是由 build/bin/build-entry.js
生成的,所以我们来到该文件:
var Components = require('../../components.json');
var fs = require('fs');
var render = require('json-templater/string');
var uppercamelcase = require('uppercamelcase');
var path = require('path');
var endOfLine = require('os').EOL;
// 输出地址
var OUTPUT_PATH = path.join(__dirname, '../../src/index.js');
// 导入模板
var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
// 安装组件模板
var INSTALL_COMPONENT_TEMPLATE = ' {{name}}';
// 模板
var MAIN_TEMPLATE = `/* Automatically generated by './build/bin/build-entry.js' */
{{include}}
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
const components = [
{{install}},
CollapseTransition
];
const install = function(Vue, opts = {}) {
locale.use(opts.locale);
locale.i18n(opts.i18n);
components.forEach(component => {
Vue.component(component.name, component);
});
Vue.use(InfiniteScroll);
Vue.use(Loading.directive);
Vue.prototype.$ELEMENT = {
size: opts.size || '',
zIndex: opts.zIndex || 2000
};
Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
Vue.prototype.$message = Message;
};
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
version: '{{version}}',
locale: locale.use,
i18n: locale.i18n,
install,
CollapseTransition,
Loading,
{{list}}
};
`;
delete Components.font;
var ComponentNames = Object.keys(Components);
var includeComponentTemplate = [];
var installTemplate = [];
var listTemplate = [];
// 根据 components.json 文件批量生成模板所需的参数
ComponentNames.forEach(name => {
var componentName = uppercamelcase(name);
includeComponentTemplate.push(render(IMPORT_TEMPLATE, {
name: componentName,
package: name
}));
if (['Loading', 'MessageBox', 'Notification', 'Message', 'InfiniteScroll'].indexOf(componentName) === -1) {
installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, {
name: componentName,
component: name
}));
}
if (componentName !== 'Loading') listTemplate.push(` ${componentName}`);
});
// 传入模板参数
var template = render(MAIN_TEMPLATE, {
include: includeComponentTemplate.join(endOfLine),
install: installTemplate.join(',' + endOfLine),
version: process.env.VERSION || require('../../package.json').version,
list: listTemplate.join(',' + endOfLine)
});
// 生成入口文件
fs.writeFileSync(OUTPUT_PATH, template);
console.log('[build entry] DONE:', OUTPUT_PATH);
build-entry.js
使用了 json-templater
来生成了入口文件。在这里,我们不关注 json-templater
的用法,仅仅研究这个文件的思想。
它通过引入 components.json
这个我们前面提到过的静态文件,批量生成了组件引入、注册的代码。这样做的好处是什么?我们不再需要每添加或删除一个组件,就在入口文件中进行多处修改,使用自动化生成入口文件之后,我们只需要修改一处即可。
另外,再说一个鬼故事:之前提到的 components.json
文件也是自动化生成的。由于本文篇幅有限,接下来就需要同学们自己去钻研啦。
总结
坏的代码各有不同,但是好的代码思想总是一致的,那就是高性能易维护,随着一个项目代码量越来越大,在很多时候,易维护的代码甚至比高性能但是难以维护的代码更受欢迎,高内聚低耦合的思想无论在何时都不会过时。
我一直坚信,我们学习各种源码不是为了盲目模仿它们的写法,而是为了学习它们的思想。毕竟,代码的写法很快就会被更多更优秀的写法替代,但是这些思想将是最宝贵的财富。
ElementUI 源码简析——源码结构篇的更多相关文章
- SpringMVC学习(一)——概念、流程图、源码简析
学习资料:开涛的<跟我学SpringMVC.pdf> 众所周知,springMVC是比较常用的web框架,通常整合spring使用.这里抛开spring,单纯的对springMVC做一下总 ...
- Flink源码阅读(一)——Flink on Yarn的Per-job模式源码简析
一.前言 个人感觉学习Flink其实最不应该错过的博文是Flink社区的博文系列,里面的文章是不会让人失望的.强烈安利:https://ververica.cn/developers-resource ...
- django-jwt token校验源码简析
一. jwt token校验源码简析 1.1 前言 之前使用jwt签发了token,里面的头部包含了加密的方式.是否有签名等,而载荷中包含用户名.用户主键.过期时间等信息,最后的签名还使用了摘要算法进 ...
- 0002 - Spring MVC 拦截器源码简析:拦截器加载与执行
1.概述 Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理.例如通过拦截器可以进行权限验证.记录请求信息的日 ...
- OpenStack之Glance源码简析
Glance简介 OpenStack镜像服务器是一套虚拟机镜像发现.注册.检索. glance架构图: Glance源码结构: glance/api:主要负责接收响应镜像管理命令的Restful请求, ...
- AFNetworking源码简析
AFNetworking基本是苹果开发中网络请求库的标配,它是一个轻量级的网络库,专门针对iOS和OS X的网络应用设计,具有模块化的架构和丰富的APIs接口,功能强大并且使用简单,深受苹果应用开发人 ...
- DRF之APIView源码简析
一. 安装djangorestframework 安装的方式有以下三种,注意,模块就叫djangorestframework. 方式一:pip3 install djangorestframework ...
- spring ioc源码简析
ClassPathXmlApplicationContext 首先我们先从平时启动spring常用的ClassPathXmlApplicationContext开始解析 ApplicationCont ...
- 并发系列(二)——FutureTask类源码简析
背景 本文基于JDK 11,主要介绍FutureTask类中的run().get()和cancel() 方法,没有过多解析相应interface中的注释,但阅读源码时建议先阅读注释,明白方法的主要的功 ...
随机推荐
- ORA-19625: error identifying file XXXXX
在RMAN备份全库的时候,将归档日志一同进行备份,结果报如下错误,可以看到是无法获得对应归档日志的报错: RMAN: ========================================= ...
- 使用scratchbox2建立交叉编译环境
使用scratchbox2建立交叉编译环境,使交叉编译不再烦人..... os:ubuntu 12.04.4 x64 1. 安装相关工具sudo apt-get install debootstrap ...
- .net core 利用Selenium和PhantomJS后台生成EChart图片
1.引用 NuGet安装: Selenium.Support Selenium.WebDriver Selenium.WebDriver.PhantomJS.CrossPlatform (分布Lin ...
- 我写的一个Qt 显示二维码( QR Code)的控件(可以去掉对 libpthread 的依赖,而且编译出的库文件可以在 vc2010 的release 模式下使用)
最近一个项目需要显示二维码,所以花了点时间(只用了一个晚上,写的很不完善),写了个显示二维码的控件.当然这个控件用到了些开源的代码,比如qrencode,所以我也打算把我的代码开源. 我的代码参考了 ...
- Delphi 中 断言 Assert 用法
procedure Assert(expr : Boolean [; const msg: string]); 用法: Assert(表达式,[显示信息]); 如果为假, assert会产生一个E ...
- ASP.NET 5 牛刀小試(二):加入第三方 DI 容器
上回介绍了 ASP.NET vNext 自带容器的基本用法,这次要试试把 ASP.NET vNext 的自带容器换成 Autofac. 这一次,在编写范例程序的过程中,光是解决 KRE 与相关套件的版 ...
- 原生Js封装的产品图片360度展示
挺简单的一段程序,但是效果不错: 1.把需要展示的36张图片先预加载到浏览器缓存里 2.给展示图片的div添加方法 3.通过鼠标左右移动的像素转换图片 在线效果预览:http://jsfiddle.n ...
- Qt+VS编译器:默认库“library”与其他库的使用冲突;使用 /NODEFAULTLIB:library(我曾经碰到过,修改qmake.conf,但我修改的是VS的IDE配置)good
找到qt安装目录下的mkspecs文件夹,在里面找到你使用的对应版本编译器,打开qmake.conf.稍等: /MD:动态链接多线程库(msvcrt.lib).使用该选项时,需要用/NODEFAULT ...
- 解释为什么.net 第一次请求比较慢
通过这个图可以很好的解释为什么第一次请求比较慢,为了提高访问速度,也便有了预编译. 关于ASP.NET网站:每个页面都编译成一个.dll文件 用Assembly.GetExecutingAssembl ...
- MySQL解决存入数据库和取出数据库时间格式问题
我们在往数据库里存数据时,时间格式正是我们想要的,然而取数据时去出现了这种情况 这明显不是我们想要的! 解决办法: 在我们配置mysql时添加一行代码: timezone:"08:00&qu ...