ElementUI 做爲當前運用的最廣的 Vue PC 端組件庫,不少 Vue 組件庫的架構都是參照 ElementUI 作的。做爲一個有夢想的前端(鹹魚),固然須要好好學習一番這套比較成熟的架構。前端
首先,咱們先來看看 ElementUI 的目錄結構,整體來講,ElementUI 的目錄結構與 vue-cli2
相差不大:vue
package.json
中指定 typing 字段的值爲 聲明的入口文件才能生效。說完了文件夾目錄,拋開那些常見的 .babelrc
、.eslintc
等文件,咱們來看看根目錄下的幾個看起來比較奇怪的文件:webpack
make new
建立組件目錄結構,包含測試代碼、入口文件、文檔。其中 make new
就是 make
命令中的一種。make
命令是一個工程化編譯工具,而 Makefile 定義了一系列的規則來制定文件變異操做,經常使用 Linux 的同窗應該不會對 Makefile 感到陌生。接下來,咱們來看看項目的入口文件。正如前面所說的,入口文件就是 src/index.js
:git
/* 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
函數中批量全局註冊各類指令、組件,掛載全局方法便可。github
ElementUI 的入口文件有兩點十分值得咱們學習:web
下面咱們來聊聊自動化生成入口文件,在此以前,有幾位同窗發現了入口文件是自動化生成的?說來羞愧,我也是在寫這篇文章的時候才發現入口文件是自動化生成的。vue-cli
咱們先來看看入口文件的第一句話:typescript
/* Automatically generated by './build/bin/build-entry.js' */
複製代碼
這句話告訴咱們,該文件是由 build/bin/build-entry.js
生成的,因此咱們來到該文件:element-ui
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
的用法,僅僅研究這個文件的思想。json
它經過引入 components.json
這個咱們前面提到過的靜態文件,批量生成了組件引入、註冊的代碼。這樣作的好處是什麼?咱們再也不須要每添加或刪除一個組件,就在入口文件中進行多處修改,使用自動化生成入口文件以後,咱們只須要修改一處便可。
另外,再說一個鬼故事:以前提到的 components.json
文件也是自動化生成的。因爲本文篇幅有限,接下來就須要同窗們本身去鑽研啦。
壞的代碼各有不一樣,可是好的代碼思想老是一致的,那就是高性能易維護,隨着一個項目代碼量愈來愈大,在不少時候,易維護的代碼甚至比高性能可是難以維護的代碼更受歡迎,高內聚低耦合的思想不管在什麼時候都不會過期。
我一直堅信,咱們學習各類源碼不是爲了盲目模仿它們的寫法,而是爲了學習它們的思想。畢竟,代碼的寫法很快就會被更多更優秀的寫法替代,可是這些思想將是最寶貴的財富。