当前位置: 移动技术网 > IT编程>开发语言>JavaScript > ElementUI 源码简析——源码结构篇

ElementUI 源码简析——源码结构篇

2019年07月19日  | 移动技术网IT编程  | 我要评论

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 等文件,我们来看看根目录下的几个看起来比较奇怪的文件:

  • .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 的入口文件有两点十分值得我们学习:

  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 => {
    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 文件也是自动化生成的。由于本文篇幅有限,接下来就需要同学们自己去钻研啦。

总结

坏的代码各有不同,但是好的代码思想总是一致的,那就是高性能易维护,随着一个项目代码量越来越大,在很多时候,易维护的代码甚至比高性能但是难以维护的代码更受欢迎,高内聚低耦合的思想无论在何时都不会过时。

我一直坚信,我们学习各种源码不是为了盲目模仿它们的写法,而是为了学习它们的思想。毕竟,代码的写法很快就会被更多更优秀的写法替代,但是这些思想将是最宝贵的财富。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网