ElementUI 作为当前运用的最广的 Vue PC 端组件库,很多 Vue 组件库的架构都是参照 ElementUI 做的。作为一个有梦想的前端(咸鱼),当然需要好好学习一番这套比较成熟的架构。
首先,我们先来看看 ElementUI 的目录结构,总体来说,ElementUI 的目录结构与 vue-cli2
相差不大:
package.json
中指定 typing 字段的值为 声明的入口文件才能生效。说完了文件夹目录,抛开那些常见的 .babelrc
、.eslintc
等文件,我们来看看根目录下的几个看起来比较奇怪的文件:
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 源码简析——源码结构篇 - 格子熊 - 博客园ElementUI 作为当前运用的最广的 Vue PC 端组件库,很多 Vue 组件库的架构都是参照 ElementUI 做的。作为一个有梦想的前端(咸鱼),当然需要好好学习一番这套比较成熟的架构。 https://www.cnblogs.com/karthuslorin/p/11202209.html