轉自:https://www.cnblogs.com/karthuslorin/p/11202209.html
目錄結構解析
首先,我們先來看看 ElementUI 的目錄結構,總體來說,ElementUI 的目錄結構與 vue-cli2
相差不大:
-
- .github:存放貢獻指南以及 issue、PR 模板,這些是一個成熟的開源項目必須具備的。
- build:毫無疑問,看文件夾名稱就知道是存放打包工具的配置文件。
- examples:存放 ElementUI 組件示例。
- packages:存放組件源碼,也是之后源碼分析的主要目標。
- src:存放入口文件以及各種輔助文件。
src/directives
:放置自定義指令。src/locale
:放置語言的配置文件。src/mixins
:放置組件用的混合文件。src/transitions
:放置動畫配置文件。src/utils
:放置用到工具函數文件。src/index.js
:組件注冊的入口文件。
- 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 的入口文件有兩點十分值得我們學習:
- 初始化時,提供選項用於配置全局屬性,大大方便了組件的使用。
- 自動化生成入口文件
自動化生成入口文件
我們先來看看入口文件的第一句話:
這句話告訴我們,該文件是由 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
這個我們前面提到過的靜態文件,批量生成了組件引入、注冊的代碼。這樣做的好處是什么?我們不再需要每添加或刪除一個組件,就在入口文件中進行多處修改,使用自動化生成入口文件之后,我們只需要修改一處即可。