工欲善其事必先利其器,既然咱们的 air-ui
大部分都是借鉴 element-ui
项目,因此本章咱们就来分析一下 element-ui
项目源码。javascript
首先咱们到这个 github.com/ElemeFE/ele… , 把源码下载下来css
由于是基于 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
首先咱们先看一些目录结构: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
theme-chalk
的目录,辨识度过低,不注意看,根本找不到,为啥不放 src 目录的 styles 目录呢?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
"bootstrap": "yarn || npm i"
复制代码
这个就是纯粹的安装依赖,没有实际操做
"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js"
复制代码
这个又是一连串指令的结合,继续拆开分析:
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 数组:
后面这个文件会在显示字体图标的时候,会拿出来作映射。 这样的好处就是,之后添加新的字体图标的时候,写文档的时候,不用手动去添加,它会自动生成这张映射表。
在启动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>
复制代码
展现页就是下图:
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
这个文件。这个文件是全部能够配置的组件的列表。一旦这个组件在列表里面,那么我打包的时候,就会把这个组件打进去。反之,若是里面有些组件,好比 drawer
和 divider
这种,个人项目根本不会用到。那么我就把这两个组件从 components.json
中剔除,那么生成的 src/index.js
这个 js,就不会包含这两个组件了。 从而实现可定制化组件的方式。
node build/bin/i18n.js
复制代码
生成这个站点的 i18n 多语言的静态页面, 经过这个任务,咱们能够生成你想要支持的多语言文件,这个多语言文件其实就是文档站点的多语言的每个页面的词条,跟组件的多语言没有关系
每个页面用到的多语言都有,从 json
来看,目前就只支持 4 种多语言:
具体看生成的站点就知道了:
就四种,那么是怎么替换的呢? 其实很简单,根据不一样的语言的不一样的页面,而后把对应的词条 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 这个页面来讲:
他其实就是把中文下的design
页面的词条取出来,而后塞到design.tpl
这个模板里面:
最后生成的结果页就是 design.vue
这个页面:
这样就把这个站点的多语言相关的页面都替换完了。
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';
复制代码
cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js
复制代码
到这一步才开始进行webpack
构建了,对应的webpack
的配置文件是webpack.demo.js
。 cross-env
是为了兼容各个OS系统的环境设置的方式。
由于咱们的 env
是 dev,而且是非 play
模式, 因此 js 执行的入口文件是 example/entry.js
这个文件。 这个也是这个项目的 入口 vue 文件 (就是初始化 vue 的那个文件,而入口文件是 index.tpl
),而后通过 webpack
打包以后,这个文件就会变成 [name].js
,而后被嵌进去到 index.tpl
这个模板文件里面:
从源码上能够看到,他生成了入口的 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
没有特别指定的话, 那么output
的 filename
中的 name
,也应该是 entry
才对,怎么会变成 main
???
后面查了一下,原来entry
配置是:
entry: './examples/entry.js'
复制代码
那么在 webpack
的解析中,就会变成:
entry: {
  main: './examples/entry.js'
}
复制代码
因此名字就会变成 main
了, 同时由于 mode 是 dev,因此还会开启本地服务:
devServer: {
host: '0.0.0.0',
port: 8085,
publicPath: '/',
hot: true
},
复制代码
这样子 webpack 打包的部分就完成了。 可是还有最后一步呢。
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 模式,咱们能够知道,首页文件是examples/index.tpl
, 入口 js 文件是 examples/entry.js
(这个其实就是vue 的入口文件), 而后经过 webpack-dev-server
启动一个热更新服务 (打包的文件都是放在内存里面的)。事实上,以上这些都没有涉及到 element-ui
是怎么编写 ui 组件库的。只是将其 api 站点运行起来而已。
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 差很少。
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
目录。
总的指令是:
"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",
复制代码
里面有些指令前面已经分析过了,这边就不细讲了:
"clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
复制代码
其实就是先清理目录:
lib
目录packages
下的 lib
目录test
目录下的测试目录npm run build:file
复制代码
同上面 dev 构建的 step 1.2
同样,不在重复讲,无非就是 初始化 icon
, 生成入口文件
, 生成 多语言静态页
, 生成版本列表文件
"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet"
复制代码
这个就是跑代码检测工具
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
文件。
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
这个文件。
这时候应该会有一个疑问??? 这两个任务打出来的文件,明显大小不同?? 那为啥同一个入口文件,能够经过不一样的配置,打出两个不同的 出口文件??
后面发现原来是有差异的,这两个其实都是入口文件,只不过 element-ui.common.js
这个是 ES6 Module
模块化加载方式的入口文件。 而 index.js
这个是 umd
通用模块化加载方式的入口文件。 他的 webpack
的 output
配置有一个这个配置:
libraryTarget: 'umd',
复制代码
标明要打包成 umd
的模块化方式的入口文件。这是一种能够将你的 library
可以在全部的模块定义下均可运行的方式。它将在 CommonJS
, AMD
环境下运行,或将模块导出到 global
下的变量,也能够用script
方式引入,像 jquery
, lodash
,underscore
这些工具库, 都是这种打包方式。
打包出来实际上是这样子:以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
。
事实上,在我本身的理解中,关于 webpack
的 libraryTarget: commonjs
其实对应就是 CommonJS
的模块化加载方式,而 libraryTarget: commonjs2
其实对应的是 es6 的 ES6 Module
的模块化加载方式,他俩的不一样之处在于:
CommonJs
导出的是变量的一份拷贝,ES6 Module
导出的是变量的绑定(export default 是特殊的)CommonJs
是单个值导出,ES6 Module
能够导出多个, 通常不提倡 export default
和 export {a, b, c}
混用。(事实上,我后面这点踩坑了)CommonJs
是动态语法能够写在判断里,ES6 Module
静态语法只能写在顶层, 并且 ES6 Module
是只读的,不能被改变ES6 Module
默认就是严格模式而之因此要用 ES6 Module
的模块化加载方式,主要是由于这边涉及到一个模块化的分类,好比:
AMD(Asynchronous Module Definition,异步模块定义)
,表明产品为:Require.js
CMD(Common Module Definition,通用模块定义)
,表明产品为:Sea.js
CommonJS
规范,具体规范是:
require
引入其余模块或者包exports
或者module.exports
导出模块成员ES6模块化
。ES6
语法规范中,在语言层面上定义了 ES6 模块化规范,是浏览器端与服务器端通用的模块化开发规范。规范以下:
import
关键字export
关键字总结就是:推荐使用ES6模块化
,由于AMD
,CMD
局限使用于浏览器端,而CommonJS
在服务器端使用。ES6模块化
是浏览器端和服务器端通用的规范。事实上,后面在作 air-ui
的时候,我直接抛弃 umd
的通用模块化加载方式,直接采用 ES6 Module
的方式来引用, 就是 import
的方式来引入(固然若是后面业务的扩展真的须要有相似于 umd
的引用方式,再把这种打包方式加上去,可是若是只是用来作 vue 项目的话,基本上都是用ES6 Module
的方式来引用)。
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'
},
复制代码
生成的这些文件其实就是单独组件的es6 Module
的模块化加载方式,是能够被单独引用的。
"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
复制代码
这个其实就是将 src
目录下的 除了 src/index.js
这个文件以外的其余全部的文件都先通过 babel
转换,而后输出到 lib
目录下:
由于这几个目录在打包的时候,是经过 externals
的方式打包的,并无打进去 element-ui.common.js
中里面去,因此这边要另外处理。因此才用这种方式 , 其实就是这 5 个目录。
"build:umd": "node build/bin/build-locale.js",
复制代码
针对 umd
的加载方式,对词条的多语言进行打包。其实就是把 src/locale/lang
这个目录下的文件拷贝到 umd
目录下,不过为了兼容 umd
的加载方式,就作了一些兼容。
"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
目录下。
这样子,整个库的 打包就完成了。
"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 插件。用来配合作主题自定义的。在本文分析中不重要。
"pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js && sh build/deploy-faas.sh",
复制代码
这个就经过几个脚本:
sh build/git-release.sh
这个脚本主要是为了检查 git 状态,若是状态是异常的,那么就继续,不然就退出sh build/release.sh
这个其实就是将 dev 分支 合并到 master 分支,而后将这个合并的 commit 提交上去,最后提交到 master 分支。node build/bin/gen-indices.js
这个就是替换多语言的文本,主要是替换索引sh build/deploy-faas.sh
更新文档站点这个在本文分析中,其实也不重要。
接下来咱们试着装一下这个第三方库:
npm install element-ui
复制代码
这个包下载下来是这样子的, 跟下面完整项目比的话, 大部分的资源仍是在的
可是也能够看出,这个用来被安装的分支,其实不是 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 的:
他所有的 scss 文件都放在 package/theme-chalk/src
这个下面:
打完包的话,其实也会在 package/theme-chalk/lib
下也会放一份打包后的:
这个操做,实际上是经过这个指令来跑的:
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
文件所有引入过去:
能够理解为这个 index.css
就是总的样式文件了。 而咱们只须要引入这个 css 就能够了。 其余的都不须要引用了。
理解了构建以后,组件的开发反而是比较好理解的。element-ui
每个组件都是一个单独的目录,虽然有时候会相互引用,可是逻辑几乎是互不干扰。遵循必定的目录结构,以 alert
这个组件为例,在 packages
目录的目录结构是这样子的:
alert
|---src/
| |---main.vue 组件的业务逻辑
|---index.js 组件的声明
复制代码
这个目录是很清晰的,因此基本上大框架搞懂了,反而组件的开发是显得比较容易的,由于耦合性很低,不会影响到其余的组件。 并且从整个构建来看,其实打包出来的都没有涉及到主题自定义的方面,打包出来的 index.css
就是默认主题, 因此它的主题自定义功能,实际上是经过用户交互界面来让用户本身手动自定义的,这个后面讲到air-ui
怎么作主题自定义的时候,会详细讲,其实并无所谓的优劣,就是使用场景不同,致使的解决方式也不同。
经过对构建的分析,大概就能够知道 element-ui
的大体脉络,固然,若是说彻底的了解,那确定是不够,事实上我也没有把全部的代码所有啃一遍,一方面是时间上不容许,另外一方面,我在获得我想要的思路和流程以后,不少东西我都是按照本身的方式和习惯写的,不必定要跟 element-ui
同样。我看源码只是为了一个思路而已,接下来说一下咱们怎么基于 element-ui
来作 air-ui
的。
系列文章: