由于面试需要,先来几发 element 源码学习博客。Vue 源码还将继续更新。
好,现在我们开始学习 element —— 最受欢迎的 Vue UI 框架。
package.json
我觉得要看一个前端项目,首先必须得看看 package.json
这个文件。
编译入口
来看看编译的入口
1 | "scripts": { |
这里我们以看源码的角度,先了解构建文件命令。其实就是 node 执行了几个 js 脚本。我们深入看下 iconInit
、 build-entry
、 i18n
、 version
这些脚本文件。
1 | // build/bin/build-all.js |
以上方法主要是获取所有组件名,然后拼接为 shell 命令,执行 shell 命令进行 build。
1 | // build/bin/iconInit.js |
以上方法通过解析 icon.scss 最终导出 icon.json 文件,该文件保存了各种图标。
1 | // build/bin/i18n.js |
以上代码是国际化的过程,最终将会在 examples/pages/
目录中生成不同语言的内容。国际化具体内容请参照 国际化。
1 | // build/bin/version.js |
获取 version,定义了一个 content,如果当前版本不在 content 中,那么再添加一个版本数据。由于我学习的版本是 2.2.1
,最终生成的结果是:1
{"1.4.13":"1.4","2.0.11":"2.0","2.1.0":"2.1","2.2.1":"2.2"}
出现这几个版本号的原因么,看下官网就能发现端倪,应该是几个重要的稳定版本。
来看下 build-entry.js
文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103// build/bin/build-entry.js
var Components = require('../../components.json'); // 组件数据
var fs = require('fs'); // node文件系统
var render = require('json-templater/string');
var uppercamelcase = require('uppercamelcase'); // 驼峰大小写写法
var path = require('path'); // node路径系统
var endOfLine = require('os').EOL;
// 导出路径
var OUTPUT_PATH = path.join(__dirname, '../../src/index.js');
// 导入template、安装组件template、主要template
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.map(component => {
Vue.component(component.name, component);
});
Vue.use(Loading.directive);
const ELEMENT = {};
ELEMENT.size = opts.size || '';
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;
Vue.prototype.$ELEMENT = ELEMENT;
};
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
module.exports = {
version: '{{version}}',
locale: locale.use,
i18n: locale.i18n,
install,
CollapseTransition,
Loading,
{{list}}
};
module.exports.default = module.exports;
`;
delete Components.font;
// 组件名
var ComponentNames = Object.keys(Components);
var includeComponentTemplate = [];
var installTemplate = [];
var listTemplate = [];
// 遍历组件名解析template
ComponentNames.forEach(name => {
var componentName = uppercamelcase(name); // 驼峰命名
includeComponentTemplate.push(render(IMPORT_TEMPLATE, {
name: componentName,
package: name
}));
if (['Loading', 'MessageBox', 'Notification', 'Message'].indexOf(componentName) === -1) {
installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, {
name: componentName,
component: name
}));
}
if (componentName !== 'Loading') listTemplate.push(` ${componentName}`);
});
// 主要template
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);
以上代码中,先是定义了三个 template,然后使用 render 方法来渲染这些 template。最后生成一个主要 template 导出为文件。render 函数中的第二个参数为 template 中 的数据。
这个 render 方法来自 json-templater 库,这个库可以将字符串编译为 js 代码。
依赖关系
看看 element 都 depend 了些什么?下面对 element 的依赖作了注释。
1 | "dependencies": { |
阿西吧,这依赖库真心多~~不知道他们如何找到这么多库的。
src目录
再来看看项目结构部分。按常理源码肯定是放在 src
目录中的,我们找到 src/index.js
。代码有点长,只贴出 install
方法部分了。说下都干了什么:导入所有组件,定义安装方法,判断环境执行 install
方法,最后整体导出。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31const install = function(Vue, opts = {}) {
locale.use(opts.locale);
locale.i18n(opts.i18n);
components.map(component => {
// 遍历将组件加入到Vue中
Vue.component(component.name, component);
});
// 加载中
Vue.use(Loading.directive);
const ELEMENT = {};
ELEMENT.size = opts.size || '';
// 定义Vue的原型 prototype
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;
Vue.prototype.$ELEMENT = ELEMENT;
};
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
从组件的导入 import Button from '../packages/button/index.js';
可以看到所有组件都是在 packages 目录下的。这部分我们会在之后重点学习。
那么问题来了,既然组件都在 packages
中了,那么 src
目录下都干了些什么呢?来看看各个目录的功能:
- directive 实现滚轮优化和避免重复点击。
- locale 用于 i18n 国际化功能。
- mixins 看样子应该是用于混合到 Vue 实例的 options 中的。
- transition 在渲染是操作style做过渡效果处理。
- utils 工具文件夹。
主要目的是项目结构,就不深入展开了。如有需要后面再讲。
其他目录
上面说过,package 目录中存放了所有 component 组件的代码。另外也存放了组件的样式 .scss
文件。
而对于type目录中,存放的 .ts
文件。都是 TypeScript 文件。但是有个问题,我不太清楚这些 .ts
都用在何处。而且在 package.json 中也未导入 TypeScript 的库,只是在更新日志中有 新增 TypeScript 类型声明
这么一句话。这点有所疑惑。
test 目录下是各个组件的单元测试用例,这部分是学习单元测试写法的很好的参考代码(我学习测试框架就是在这里学的)。需要学习单元测试的可以深入看看。
example 目录下是 element 的示例项目。我们的目的是学习源码,所以这部分先忽略~
最后
简单了解了下项目的编译、项目的依赖库情况、项目的机构。下一篇开始学习一些组件的实现。逐步深入扒开element的神秘面纱。