自建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 的。


系列文章:

相關文章
相關標籤/搜索