自建vue组件 air-ui (2) -- 先分析一下 element ui 项目

前言

工欲善其事必先利其器,既然咱们的 air-ui 大部分都是借鉴 element-ui 项目,因此本章咱们就来分析一下 element-ui 项目源码。javascript

下载源码

首先咱们到这个 github.com/ElemeFE/ele… , 把源码下载下来css

安装 yarn

由于是基于 yarn 安装的,因此咱们也全局装一下 yarn:html

F:\code\github\element>npm install -g yarn
C:\Program Files\nodejs\yarn -> C:\Program Files\nodejs\node_modules\yarn\bin\yarn.js
C:\Program Files\nodejs\yarnpkg -> C:\Program Files\nodejs\node_modules\yarn\bin\yarn.js
+ yarn@1.19.1
added 1 package in 7.777s
复制代码

安装成功,试一下指令是否正常:vue

F:\code\github\element>yarn --version
1.19.1
复制代码

本地环境跑起来

element-ui 的当前版本是 2.12.0 (我在写这个文章的时候,其实已经更新到 2.13.0,可是差异不大,都是一些细节的调整)java

F:\code\github\element>npm run dev

> element-ui@2.12.0 dev F:\code\github\element
> npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js


> element-ui@2.12.0 bootstrap F:\code\github\element
> yarn || npm i

yarn install v1.19.1
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.7: The platform "win32" is incompatible with this module.
info "fsevents@1.2.7" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning " > karma-webpack@3.0.5" has incorrect peer dependency "webpack@^2.0.0 || ^3.0.0".
[4/4] Building fresh packages...
Done in 202.16s.
。。。
i 「wdm」: Compiled successfully.
复制代码

第一次的时候,会先安装依赖。在本地浏览器打开 localhost:8085,能够看到跟官网的站点同样:node

1

目录结构

首先咱们先看一些目录结构:jquery

element
  |---build/       构建相关的文件
  |---example/     放置element api的页面文档
  |---lib/         组件构建打包以后的存放目录
  |---packages/    放置element的组件(css样式放置在这个目录下theme-chalk下)
  |     |---button/            button 组件的目录
  |     |     |---src/         button 组件的业务代码
  |     |     |---index.js     button 组件的定义文件
  ... 这里面 N 个相同目录格式的组件 ...
  |     |---theme-chalk/       存放 scss 样式
  |     |     |---lib/         scss 打包以后的css文件
  |     |     |---src/         scss 样式文件
  |     |     |---gulpfiles    gulp 构建任务,将src目录中的 scss 打包成 css并放到 lib 目录
  |---src/
  |     |---directives/     放置自定义指令
  |     |---locale/         放置语言的配置文件
  |     |     |---lang/     放置多语言
  |     |---mixins/         放置组件用的混合文件
  |     |---transitions/    放置动画配置文件
  |     |---utils/          放置用到工具函数文件
  |     |---index.js        组件注册的入口文件
  |---test/        测试文件
  |---types/       typescript 的数据类,用来给 IDE 在写 ts 代码时候的提示
  |---components.json   存放单独导出组件的json文件
  |---package.json
复制代码

能够看到几个有点奇怪的地方:webpack

  1. 存放组件的目录,居然不是在 src 的 components 目录中,而是单独抽出来
  2. 存放css的目录,居然跟组件是同级,并且藏在了一个叫作theme-chalk的目录,辨识度过低,不注意看,根本找不到,为啥不放 src 目录的 styles 目录呢?
  3. 文档站点所在的 example 的目录结构,也有点乱,至少第一眼看过去,都是看不懂

1. dev 环境的构建

npm run dev
复制代码

想要更快的了解一个项目,除了将环境跑起来以后,另外一个就是分析构建的方式了,因此接下来咱们分析一下 dev 环境的构建方式,首先从指令来看:git

"dev": "npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js",
复制代码

这个实际上是一连串指令结合的,咱们一个一个分析:es6

step 1.1

"bootstrap": "yarn || npm i"
复制代码

这个就是纯粹的安装依赖,没有实际操做

step 1.2

"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js"
复制代码

这个又是一连串指令的结合,继续拆开分析:

stpe 1.2.1

node build/bin/iconInit.js
复制代码

简单的来讲,就是把packages/theme-chalk/src/icon.scss 这个 css 文件中的全部的 icon 描述字符都提取出来,好比这个:

.el-icon-turn-off:before {
  content: "\e734";
}
复制代码

其实就是把turn-off这个子串提取出来,而后放入到这个examples/icon.json 文件里面,这个文件其实就是一个大的 json 数组:

1

后面这个文件会在显示字体图标的时候,会拿出来作映射。 这样的好处就是,之后添加新的字体图标的时候,写文档的时候,不用手动去添加,它会自动生成这张映射表。

在启动dev模式的时候,入口的 js 文件examples/entry.js 就会把这个 json 文件引入进入:

import icon from './icon.json';

Vue.prototype.$icon = icon; // Icon 列表页用
复制代码

会在 icon 列表的时候,用上这个映射表: 以中文为例,那么文件就在 examples/docs/zh-CN/icon.md 这个展现页用到

### 图标集合

<ul class="icon-list">
  <li v-for="name in $icon" :key="name">
    <span>
      <i :class="'el-icon-' + name"></i>
      <span class="icon-name">{{'el-icon-' + name}}</span>
    </span>
  </li>
</ul>
复制代码

展现页就是下图:

1

step 1.2.2

node build/bin/build-entry.js
复制代码

生成入口文件 index.js, 简单的来讲,就是经过这个任务,来生成 src 目录下的 index.js (每次打包,这个 index.js 都会从新生成), 这个也是整个组件库的入口文件。 他这个有一个模板,里面有一些组件是内置会有的,好比:

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;

export default {
  version: '{{version}}',
  locale: locale.use,
  i18n: locale.i18n,
  install,
  CollapseTransition,
  Loading,
{{list}}
};
复制代码

他是经过获取根目录下的components.json 这个文件。这个文件是全部能够配置的组件的列表。一旦这个组件在列表里面,那么我打包的时候,就会把这个组件打进去。反之,若是里面有些组件,好比 drawerdivider 这种,个人项目根本不会用到。那么我就把这两个组件从 components.json 中剔除,那么生成的 src/index.js 这个 js,就不会包含这两个组件了。 从而实现可定制化组件的方式。

step 1.2.3

node build/bin/i18n.js
复制代码

生成这个站点的 i18n 多语言的静态页面, 经过这个任务,咱们能够生成你想要支持的多语言文件,这个多语言文件其实就是文档站点的多语言的每个页面的词条,跟组件的多语言没有关系

1

每个页面用到的多语言都有,从 json 来看,目前就只支持 4 种多语言:

1

具体看生成的站点就知道了:

1

就四种,那么是怎么替换的呢? 其实很简单,根据不一样的语言的不一样的页面,而后把对应的词条 json 传进去替换就好了。

Object.keys(lang.pages).forEach(page => {
  var templatePath = path.resolve(__dirname, `../../examples/pages/template/${ page }.tpl`);
  var outputPath = path.resolve(__dirname, `../../examples/pages/${ lang.lang }/${ page }.vue`);
  var content = fs.readFileSync(templatePath, 'utf8');
  var pairs = lang.pages[page];

  Object.keys(pairs).forEach(key => {
    content = content.replace(new RegExp(`<%=\\s*${ key }\\s*>`, 'g'), pairs[key]);
  });

  fs.writeFileSync(outputPath, content);
});
复制代码

模板就是 .tpl,结果页就是 .vue 文件, 举个例子,就以 design 这个页面来讲:

1

他其实就是把中文下的design页面的词条取出来,而后塞到design.tpl这个模板里面:

1

最后生成的结果页就是 design.vue 这个页面:

1

这样就把这个站点的多语言相关的页面都替换完了。

step 1.2.4

node build/bin/version.js
复制代码

生成对应的版本列表文件 examples/version.json, 这个没啥好说的,硬编码写入到一个文件而已。 这个文件会在 changelog 这个页面的时候, 动态用 ajax 来请求:

xhr.open('GET', '/versions.json');
复制代码

而后再结合根目录下的 CHANGELOG.{lang}.md 来显示出真正的 log 列表, 这个 md 文件会在 changlog.vue 文件中被引用进去:

import ChangeLog from '../../../CHANGELOG.zh-CN.md';
复制代码

step 1.3

cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js
复制代码

到这一步才开始进行webpack构建了,对应的webpack的配置文件是webpack.demo.jscross-env 是为了兼容各个OS系统的环境设置的方式。

由于咱们的 env 是 dev,而且是非 play 模式, 因此 js 执行的入口文件是 example/entry.js 这个文件。 这个也是这个项目的 入口 vue 文件 (就是初始化 vue 的那个文件,而入口文件是 index.tpl),而后通过 webpack 打包以后,这个文件就会变成 [name].js,而后被嵌进去到 index.tpl 这个模板文件里面:

1

从源码上能够看到,他生成了入口的 js 文件以后,就嵌进去了 index.tpl 这个文件里面。而index.tpl 这个模板文件,也会在打包的时候,进行 webpack 的参数填充,从而变成 真正的入口文件 index.html

new HtmlWebpackPlugin({
  template: './examples/index.tpl',
  filename: './index.html',
  favicon: './examples/favicon.ico'
}),
复制代码

至于为何这个名字 [name]main,这个确实有点奇怪,按照道理说,个人入口文件是 entry.js ,若是output 没有特别指定的话, 那么outputfilename 中的 name,也应该是 entry 才对,怎么会变成 main ???

后面查了一下,原来entry 配置是:

entry: './examples/entry.js'
复制代码

那么在 webpack 的解析中,就会变成:

entry: {
&emsp;&emsp;main: './examples/entry.js'
}
复制代码

因此名字就会变成 main 了, 同时由于 mode 是 dev,因此还会开启本地服务:

devServer: {
  host: '0.0.0.0',
  port: 8085,
  publicPath: '/',
  hot: true
},
复制代码

这样子 webpack 打包的部分就完成了。 可是还有最后一步呢。

step 1.4

node build/bin/template.js
复制代码

其实这一步就是watch,针对全部的页面模板,进行监听,其实就是针对 examples/pages/template 这个目录下的全部的模板,进行监听,若是有修改的话,就从新执行 npm run i18n 任务,也就是

"i18n": "node build/bin/i18n.js",
复制代码

其实就是 1.2.3 的任务,就是为模板插入多语言, 注意,这时候的 __dirname 就是 webpack 运行环境的内存中的目录了,一旦 tpl 文件变了,就会致使对应的 vue 文件改变,从而致使 webpack 从新 reload,而后界面就刷新了。由于用的是 webpack-dev-server 这个 server, 他有自带监听机制。rules 里面的 loader 相关的文件一旦修改的话,是会热更新的:

{
  test: /\.vue$/,
  loader: 'vue-loader',
  options: {
    compilerOptions: {
      preserveWhitespace: false
    }
  }
},
复制代码

dev 打包总结

最后总结一下:经过打包 dev 模式,咱们能够知道,首页文件是examples/index.tpl , 入口 js 文件是 examples/entry.js (这个其实就是vue 的入口文件), 而后经过 webpack-dev-server 启动一个热更新服务 (打包的文件都是放在内存里面的)。事实上,以上这些都没有涉及到 element-ui 是怎么编写 ui 组件库的。只是将其 api 站点运行起来而已。

2.文档站点的 build 版本

npm run deploy:build
复制代码

以前是文档站点的本地环境版本,也就是运行在内存的,接下来这个是线上部署版本,就是打包成静态文件:

F:\code\github\element>npm run deploy:build

> element-ui@2.12.0 deploy:build F:\code\github\element
> npm run build:file && cross-env NODE_ENV=production webpack --config build/webpack.demo.js && echo element.eleme.io>>examples/element-ui/CNAME
...
复制代码

跟开发模式有点像, 只不过这时候就输出到 examples 目录下了,而不是输出到内存了。 整个静态文件都输出到 examples/element-ui/ 这个目录里面,至关因而一个完整的站点。若是放到 webserver 下面。就是一个静态站点。 这个就不分析,由于原理跟 dev 差很少。

3.打包组件库

npm run dist
复制代码

具体log:

F:\code\github\element>npm run dist

> element-ui@2.12.0 dist F:\code\github\element
> npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:um
d && npm run build:theme
...
复制代码

目标目录就是 lib 目录,原先的项目是没有 lib 目录的, 打完包,才会有 lib 目录。

1

总的指令是:

"dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme",
复制代码

里面有些指令前面已经分析过了,这边就不细讲了:

step 3.1

"clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
复制代码

其实就是先清理目录:

  • 清理 lib 目录
  • 清理 packages 下的 lib 目录
  • 清理 test 目录下的测试目录

step 3.2

npm run build:file
复制代码

同上面 dev 构建的 step 1.2 同样,不在重复讲,无非就是 初始化 icon生成入口文件生成 多语言静态页生成版本列表文件

step 3.3

"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet"
复制代码

这个就是跑代码检测工具

step 3.4

webpack --config build/webpack.conf.js
复制代码

webpack 打包,他是把整个 webpack 的打包任务分红了好几块,这个是第一块,这一块的处理其实很简单, 就是将 src/index.js 这个入口文件进行打包,而后生成 umd 通用加载模式的文件:

mode: 'production',
entry: {
  app: ['./src/index.js']
},
output: {
  path: path.resolve(process.cwd(), './lib'),
  publicPath: '/dist/',
  filename: 'index.js',
  chunkFilename: '[id].js',
  libraryTarget: 'umd',
  libraryExport: 'default',
  library: 'ELEMENT',
  umdNamedDefine: true,
  globalObject: 'typeof self !== \'undefined\' ? self : this'
},
复制代码

这时候个人 lib 目录就生成了,采用 umd 模块化加载的方式来打包入口文件 index.js 文件。

step 3.5

webpack --config build/webpack.common.js
复制代码
mode: 'production',
entry: {
  app: ['./src/index.js']
},
output: {
  path: path.resolve(process.cwd(), './lib'),
  publicPath: '/dist/',
  filename: 'element-ui.common.js',
  chunkFilename: '[id].js',
  libraryExport: 'default',
  library: 'ELEMENT',
  libraryTarget: 'commonjs2'
},
复制代码

这个是另一种模块化方式的打包, 这个是将入口文件src/index.js 打包成 element-ui.common.js 这个文件。

1

这时候应该会有一个疑问??? 这两个任务打出来的文件,明显大小不同?? 那为啥同一个入口文件,能够经过不一样的配置,打出两个不同的 出口文件??

后面发现原来是有差异的,这两个其实都是入口文件,只不过 element-ui.common.js 这个是 ES6 Module 模块化加载方式的入口文件。 而 index.js 这个是 umd 通用模块化加载方式的入口文件。 他的 webpackoutput 配置有一个这个配置:

libraryTarget: 'umd',
复制代码

标明要打包成 umd 的模块化方式的入口文件。这是一种能够将你的 library 可以在全部的模块定义下均可运行的方式。它将在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量,也能够用script方式引入,像 jquery, lodashunderscore这些工具库, 都是这种打包方式。

打包出来实际上是这样子:以jquery 为例:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS之类的
        module.exports = factory(require('jquery'));
    } else {
        // 浏览器全局变量(root 即 window)
        root.returnExports = factory(root.jQuery);
    }
}(this, function ($) {
    // 方法
    function myFunc(){};
 
    // 暴露公共方法
    return myFunc;
}));
复制代码

其实就是判断环境,以兼容各类模块加载方式。 因此事实上,打完包的这个 index.js 开头就是这个:

!function (e, t) {
  "object" == typeof exports && "object" == typeof module ? module.exports = t(require("vue")) : "function" == typeof define && define.amd ? define("ELEMENT", ["vue"], t) : "object" == typeof exports ? exports.ELEMENT = t(require("vue")) : e.ELEMENT = t(e.Vue)
}("undefined" != typeof self ? self : this, function (e) {
  return function (e) {
    var t = {};
复制代码

而本步骤的这种方式,其实就是打包成相似于 CommonJs 的方式,只须要将全部的依赖文件都打包成一个文件便可。 而上面的 libraryTarget: commonjs2 其实跟 libraryTarget: commonjs 差很少,只不过 commonjs2 导出的是 module.exports.default, 按照个人理解,他其实就是 es6 提出的模块加载机制 ES6 Module

事实上,在我本身的理解中,关于 webpacklibraryTarget: commonjs 其实对应就是 CommonJS 的模块化加载方式,而 libraryTarget: commonjs2 其实对应的是 es6 的 ES6 Module 的模块化加载方式,他俩的不一样之处在于:

  1. CommonJs 导出的是变量的一份拷贝,ES6 Module 导出的是变量的绑定(export default 是特殊的)
  2. CommonJs是单个值导出,ES6 Module能够导出多个, 通常不提倡 export defaultexport {a, b, c} 混用。(事实上,我后面这点踩坑了)
  3. CommonJs是动态语法能够写在判断里,ES6 Module静态语法只能写在顶层, 并且 ES6 Module 是只读的,不能被改变
  4. ES6 Module 默认就是严格模式

而之因此要用 ES6 Module 的模块化加载方式,主要是由于这边涉及到一个模块化的分类,好比:

  1. 浏览器端的模块化有两个表明:
    1. AMD(Asynchronous Module Definition,异步模块定义),表明产品为:Require.js
    2. CMD(Common Module Definition,通用模块定义),表明产品为:Sea.js
  2. 服务器端的模块化规范是使用CommonJS规范,具体规范是:
    1. 使用require引入其余模块或者包
    2. 使用exports或者module.exports导出模块成员
    3. 一个文件就是一个模块,都拥有独立的做用域
  3. 大一统的模块化规范 – ES6模块化ES6 语法规范中,在语言层面上定义了 ES6 模块化规范,是浏览器端与服务器端通用的模块化开发规范。规范以下:
    1. 每个js文件都是独立的模块
    2. 导入模块成员使用import关键字
    3. 暴露模块成员使用export关键字

总结就是:推荐使用ES6模块化,由于AMDCMD局限使用于浏览器端,而CommonJS在服务器端使用。ES6模块化是浏览器端和服务器端通用的规范。事实上,后面在作 air-ui的时候,我直接抛弃 umd 的通用模块化加载方式,直接采用 ES6 Module 的方式来引用, 就是 import 的方式来引入(固然若是后面业务的扩展真的须要有相似于 umd 的引用方式,再把这种打包方式加上去,可是若是只是用来作 vue 项目的话,基本上都是用ES6 Module 的方式来引用)。

step 3.6

webpack --config build/webpack.component.js
复制代码

接下来将单独的组件也打包出来

const Components = require('../components.json');

mode: 'production',
entry: Components,
output: {
  path: path.resolve(process.cwd(), './lib'),
  publicPath: '/dist/',
  filename: '[name].js',
  chunkFilename: '[id].js',
  libraryTarget: 'commonjs2'
},
复制代码

1

生成的这些文件其实就是单独组件的es6 Module 的模块化加载方式,是能够被单独引用的。

step 3.7

"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
复制代码

这个其实就是将 src 目录下的 除了 src/index.js 这个文件以外的其余全部的文件都先通过 babel 转换,而后输出到 lib 目录下:

1

由于这几个目录在打包的时候,是经过 externals 的方式打包的,并无打进去 element-ui.common.js 中里面去,因此这边要另外处理。因此才用这种方式 , 其实就是这 5 个目录。

step 3.8

"build:umd": "node build/bin/build-locale.js",
复制代码

针对 umd 的加载方式,对词条的多语言进行打包。其实就是把 src/locale/lang 这个目录下的文件拷贝到 umd 目录下,不过为了兼容 umd 的加载方式,就作了一些兼容。

step 3.9

"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk"
复制代码

这个其实就是将 scss 文件编译成 css 文件 而且跟字体文件一块儿拷贝到 lib 目录下的 theme-chalk 目录下。

这样子,整个库的 打包就完成了。

4.打包chrome插件和发布chrome插件

"deploy:extension": "cross-env NODE_ENV=production webpack --config build/webpack.extension.js",
"dev:extension": "rimraf examples/extension/dist && cross-env NODE_ENV=development webpack --watch --config build/webpack.extension.js",
复制代码

这个是关于组件对应的 chrome 插件。用来配合作主题自定义的。在本文分析中不重要。

5.发布到线上

"pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js && sh build/deploy-faas.sh",
复制代码

这个就经过几个脚本:

  1. sh build/git-release.sh 这个脚本主要是为了检查 git 状态,若是状态是异常的,那么就继续,不然就退出
  2. sh build/release.sh 这个其实就是将 dev 分支 合并到 master 分支,而后将这个合并的 commit 提交上去,最后提交到 master 分支。
  3. node build/bin/gen-indices.js 这个就是替换多语言的文本,主要是替换索引
  4. sh build/deploy-faas.sh 更新文档站点

这个在本文分析中,其实也不重要。

使用一下

接下来咱们试着装一下这个第三方库:

npm install element-ui
复制代码

1

这个包下载下来是这样子的, 跟下面完整项目比的话, 大部分的资源仍是在的

1

可是也能够看出,这个用来被安装的分支,其实不是 master 分支的代码,而是另一个分支的代码。

具体在项目中引用是这样的:

import Vue from 'vue'
import Element from 'element-ui'

Vue.use(Element)

// orimport {
  Select,
  Button
  // ...
} from 'element-ui'

Vue.component(Select.name, Select)
Vue.component(Button.name, Button)
复制代码

这时候代码引用的 element-ui 其实就是 package.json 中的:

"main": "lib/element-ui.common.js",
"name": "element-ui",
复制代码

这两个配置,也就是实际引用的是 element-ui.common.js 这个 js,咱们看下这个 js 打包后是什么样子的,应该是能够导出各类组件的, 看了一下,他的入口仍是原来的 src/index.js 这个文件:

ps: 事实上我后面又从新试了一下,其实引入的并非 element-ui.common.js 这个文件,而是 lib/index.js 这个文件,因此仍是经过用 umd 的模块化加载方式去实现组件的按需加载功能。

version: '2.12.0',
  locale: lib_locale_default.a.use,
  i18n: lib_locale_default.a.i18n,
  install: src_install,
  CollapseTransition: collapse_transition_default.a,
  Loading: packages_loading,
  Pagination: packages_pagination,
  Dialog: dialog,
  Autocomplete: packages_autocomplete,
  Dropdown: packages_dropdown,
  DropdownMenu: packages_dropdown_menu,
  DropdownItem: packages_dropdown_item,
  Menu: packages_menu,
  Submenu: packages_submenu,
  MenuItem: packages_menu_item,
  MenuItemGroup: packages_menu_item_group,
  Input: packages_input,
  InputNumber: packages_input_number,
  Radio: packages_radio,
  RadioGroup: packages_radio_group,
  RadioButton: packages_radio_button,
  Checkbox: packages_checkbox,
  CheckboxButton: packages_checkbox_button,
  CheckboxGroup: packages_checkbox_group,
  Switch: packages_switch,
  Select: packages_select,
  Option: packages_option,
  OptionGroup: packages_option_group,
  Button: packages_button,
  ButtonGroup: packages_button_group,
  Table: packages_table,
  TableColumn: packages_table_column,
  DatePicker: packages_date_picker,
  TimeSelect: packages_time_select,
  TimePicker: packages_time_picker,
  Popover: popover,
  Tooltip: packages_tooltip,
  MessageBox: message_box,
  Breadcrumb: packages_breadcrumb,
  BreadcrumbItem: packages_breadcrumb_item,
  Form: packages_form,
  FormItem: packages_form_item,
  Tabs: packages_tabs,
  TabPane: packages_tab_pane,
  Tag: packages_tag,
  Tree: packages_tree,
  Alert: packages_alert,
  Notification: notification,
  Slider: slider,
  Icon: packages_icon,
  Row: packages_row,
  Col: packages_col,
  Upload: packages_upload,
  Progress: packages_progress,
  Spinner: packages_spinner,
  Message: packages_message,
  Badge: badge,
  Card: card,
  Rate: rate,
  Steps: packages_steps,
  Step: packages_step,
  Carousel: carousel,
  Scrollbar: scrollbar,
  CarouselItem: carousel_item,
  Collapse: packages_collapse,
  CollapseItem: packages_collapse_item,
  Cascader: packages_cascader,
  ColorPicker: color_picker,
  Transfer: transfer,
  Container: packages_container,
  Header: header,
  Aside: aside,
  Main: packages_main,
  Footer: footer,
  Timeline: timeline,
  TimelineItem: timeline_item,
  Link: packages_link,
  Divider: divider,
  Image: packages_image,
  Calendar: calendar,
  Backtop: backtop,
  InfiniteScroll: infinite_scroll,
  PageHeader: page_header,
  CascaderPanel: packages_cascader_panel,
  Avatar: avatar,
  Drawer: drawer
});

/***/ })
/******/ ])["default"];
复制代码

若是是直接导出的 Element,其实就是包含了这些组件的一个大的对象。固然还得导入 css:

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
复制代码

至于为啥要单独引用 css, 而不是把 css 也经过 webpack 打到 js 里面去。这个是由于组件式的开发, 所涉及到的全部的样式都不会写在 vue 文件里面。这个最大的缘由就是后面若是要自定义主题的话,能够直接引用自定义主题的 css 文件。而不须要管这个默认主题的 css 文件。

element-ui 为例,他全部的组件都是 js 加 vue, 而 vue 文件里面所有都没有 scss 的:

1

他所有的 scss 文件都放在 package/theme-chalk/src 这个下面:

1

打完包的话,其实也会在 package/theme-chalk/lib 下也会放一份打包后的:

1

这个操做,实际上是经过这个指令来跑的:

gulp build --gulpfile packages/theme-chalk/gulpfile.js
复制代码

简单的来讲,就是将 theme-chalk 目录下 src 下的 scss 文件编译成 css, 放到 theme-chalk 目录下的 lib 目录,同时将字体文件也一块儿挪过去。最后再把编译好的 theme-chalk/lib 目录 拷贝到 lib/theme-chalk, 就能够了

cp-cli packages/theme-chalk/lib lib/theme-chalk
复制代码

这样就实现了 scss 的编译,以及 css 文件的单独引入机制。 并且从代码里面看,这个入口的 scss 文件,其实就是将其余的 scss 文件所有引入过去:

1

能够理解为这个 index.css 就是总的样式文件了。 而咱们只须要引入这个 css 就能够了。 其余的都不须要引用了。

1

组件的开发

理解了构建以后,组件的开发反而是比较好理解的。element-ui 每个组件都是一个单独的目录,虽然有时候会相互引用,可是逻辑几乎是互不干扰。遵循必定的目录结构,以 alert 这个组件为例,在 packages 目录的目录结构是这样子的:

alert
  |---src/       
  |      |---main.vue      组件的业务逻辑
  |---index.js    组件的声明
复制代码

这个目录是很清晰的,因此基本上大框架搞懂了,反而组件的开发是显得比较容易的,由于耦合性很低,不会影响到其余的组件。 并且从整个构建来看,其实打包出来的都没有涉及到主题自定义的方面,打包出来的 index.css 就是默认主题, 因此它的主题自定义功能,实际上是经过用户交互界面来让用户本身手动自定义的,这个后面讲到air-ui怎么作主题自定义的时候,会详细讲,其实并无所谓的优劣,就是使用场景不同,致使的解决方式也不同。

总结

经过对构建的分析,大概就能够知道 element-ui 的大体脉络,固然,若是说彻底的了解,那确定是不够,事实上我也没有把全部的代码所有啃一遍,一方面是时间上不容许,另外一方面,我在获得我想要的思路和流程以后,不少东西我都是按照本身的方式和习惯写的,不必定要跟 element-ui 同样。我看源码只是为了一个思路而已,接下来说一下咱们怎么基于 element-ui 来作 air-ui 的。


系列文章:

相关文章
相关标签/搜索