elementui解读(1)源码结构篇
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 等⽂件,我们来看看根⽬录下的⼏个看起来⽐较奇怪的⽂件:
.l:持续集成(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 => {
Vueponent(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 = firm;
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 的⼊⼝⽂件有两点⼗分值得我们学习:
1. 初始化时,提供选项⽤于配置全局属性,⼤⼤⽅便了组件的使⽤,具体的可以参考我之前的那篇⽂章。
2. ⾃动化⽣成⼊⼝⽂件
⾃动化⽣成⼊⼝⽂件
下⾯我们来聊聊⾃动化⽣成⼊⼝⽂件,在此之前,有⼏位同学发现了⼊⼝⽂件是⾃动化⽣成的?说来羞愧,我也是在写这篇⽂章的时候才发现⼊⼝⽂件是⾃动化⽣成的。
我们先来看看⼊⼝⽂件的第⼀句话:
/* 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 => {
Vueponent(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 = firm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
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,
asp查看源码配置ui
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: v.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 ⽂件也是⾃动化⽣成的。由于本⽂篇幅有限,接下来就需要同学们⾃⼰去钻研啦。
总结
坏的代码各有不同,但是好的代码思想总是⼀致的,那就是⾼性能易维护,随着⼀个项⽬代码量越来越⼤,在很多时候,易维护的代码甚⾄⽐⾼性能但是难以维护的代码更受欢迎,⾼内聚低耦合的思想⽆论在何时都不会过时。
我⼀直坚信,我们学习各种源码不是为了盲⽬模仿它们的写法,⽽是为了学习它们的思想。毕竟,代码的写法很快就会被更多更优秀的写法替代,但是这些思想将是最宝贵的财富。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。