ElementUI的結(jié)構(gòu)與源碼研究(未完待續(xù))

說(shuō)明:本文基于element-ui@2.13.0愿险,源碼詳見element慢睡。
內(nèi)容目錄:

一、代碼結(jié)構(gòu)及工程化
1.1 package.json主要關(guān)注點(diǎn)

1.1.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

1.1.2 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.1.3 dev
1.1.4 test
二、src分析
2.1 directives:mousewheel & repeat-click
2.2 locale(國(guó)際化)
2.3 mixins
2.4 transitions
三坪圾、組件
四、主題
五惑朦、examples分析

一兽泄、代碼結(jié)構(gòu)及工程化

代碼結(jié)構(gòu)

components.json是一份組件清單,將在下面多處用到:

{
  "pagination": "./packages/pagination/index.js",
  "dialog": "./packages/dialog/index.js",
  "autocomplete": "./packages/autocomplete/index.js",
  "dropdown": "./packages/dropdown/index.js",
  "dropdown-menu": "./packages/dropdown-menu/index.js",
  "dropdown-item": "./packages/dropdown-item/index.js",
  "menu": "./packages/menu/index.js",
  "submenu": "./packages/submenu/index.js",
  "menu-item": "./packages/menu-item/index.js",
  "menu-item-group": "./packages/menu-item-group/index.js",
  "input": "./packages/input/index.js",
  .......
  "drawer": "./packages/drawer/index.js",
  "popconfirm": "./packages/popconfirm/index.js"
}

1.1 package.json主要關(guān)注點(diǎn)

  • 對(duì)外發(fā)布的內(nèi)容有["lib", "src", "packages", "types"]漾月;其中l(wèi)ib是運(yùn)行打包命令后生成的目錄
  • scripts中主要關(guān)注dist病梢、pubdevtest命令

1.1.1 dist

dist命令主要有9個(gè)步驟,如下:

"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
"
  • npm run clean:
    刪除上次打包生成的目錄及文件蜓陌,主要有lib目錄觅彰、test目錄以及package/theme-chalk/lib(跟主題有關(guān),后文詳講)目錄

  • npm run build:file:
    利用postcss钮热,根據(jù)package/theme-chalk/src/icon.scss填抬,往example目錄生成icon相關(guān)的信息;
    利用json-templater/string模板引擎隧期,根據(jù)根目錄下components.json飒责,往src目錄下生成index.js文件,index.js主要是引入packages目錄下的組件及install(vue插件)方法仆潮,并對(duì)外export宏蛉;
    利用正則,根據(jù)examples/i18n/page.jsonexamples/pages/template性置,生成不同語(yǔ)言的文件拾并,examples的內(nèi)容相當(dāng)于element UI官網(wǎng),后面詳講

  • npm run lint:
    利用eslint鹏浅,根據(jù).eslintrc.eslintignore文件嗅义,檢測(cè)代碼規(guī)范

  • webpack --config build/webpack.conf.js
    入口文件:src/index.js(npm run build:file生成)
    輸出:以umd形式輸出到lib/index.js
    loader:babel-loader處理jsx等文件篡石;vue-loader處理packages下面的vue組件

  • webpack --config build/webpack.common.js
    入口文件:src/index.js(npm run build:file生成)
    輸出:以commonjs2形式輸出到lib/element-ui.common.js芥喇;
    loader:babel-loader處理jsx、babel和es6等文件凰萨;vue-loader處理packages下面的vue組件继控;style-loader和css-loader處理css文件;以u(píng)rl-loader處理圖片等胖眷;

  • webpack --config build/webpack.component.js
    入口文件:components.json武通,包含packages下的組件;
    輸出:把packages下的組件珊搀,以commonjs2形式分別輸出到lib目錄冶忱;
    loader:babel-loader處理jsx、babel和es6等文件境析;vue-loader處理packages下面的vue組件囚枪;style-loader和css-loader處理css文件;以u(píng)rl-loader處理圖片等劳淆;
    按需引入:這里打包出來(lái)的內(nèi)容如下圖链沼,可以安組件打包,方便按需引入:

    按組件打包

    過(guò)程如下
    a.import { Button } from 'element-ui'
    b.借助babel插件babel-plugin-component(具體可參考babel-plugin-import的配置項(xiàng)沛鸵、針對(duì)iview進(jìn)行優(yōu)化:babel-plugin-import-custom)括勺,可以把a(bǔ)步驟的代碼轉(zhuǎn)換成下面形式:

var button = require('element-ui/lib/button') // lib/button.js即按組件打包后的el-button組件
require('element-ui/lib/theme-chalk/button.css')

該插件對(duì)應(yīng)的.babelrc相關(guān)配置:

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
        "libraryDirectory": "lib", // default: lib
      }
    ]
  ]
}

但是

element-ui 這種按需引入的方式雖然方便缆八,但背后卻要解決幾個(gè)問(wèn)題,由于我們支持每個(gè)組件可以單獨(dú)引入疾捍,那么如果產(chǎn)生了組件依賴并且同時(shí)按需引入的時(shí)候奈辰,代碼冗余問(wèn)題怎么解決。舉個(gè)例子乱豆,在 element-ui 中奖恰,Table 組件依賴了 CheckBox 組件,那么當(dāng)我同時(shí)引入了 Table 組件和 CheckBox 組件的時(shí)候宛裕,會(huì)不會(huì)產(chǎn)生代碼冗余呢房官?

import { Table, CheckBox } from 'element-ui'

如果你不做任何處理的話,答案是會(huì)续滋,你最終引入的包會(huì)有 2 份 CheckBox 的代碼。那么 element-ui 是怎么解決這個(gè)問(wèn)題的呢孵奶?實(shí)際上只是部分解決了疲酌,它的 webpack 配置文件中配置了 externals,在 build/config.js 中我們可以看到這些具體的配置:

var externals = {};

Object.keys(Components).forEach(function(key) {
 externals[`element-ui/packages/${key}`] = `element-ui/lib/${key}`;
});

externals['element-ui/src/locale'] = 'element-ui/lib/locale';
utilsList.forEach(function(file) {
 file = path.basename(file, '.js');
 externals[`element-ui/src/utils/${file}`] = `element-ui/lib/utils/${file}`;
});
mixinsList.forEach(function(file) {
 file = path.basename(file, '.js');
 externals[`element-ui/src/mixins/${file}`] = `element-ui/lib/mixins/${file}`;
});
transitionList.forEach(function(file) {
 file = path.basename(file, '.js');
 externals[`element-ui/src/transitions/${file}`] = `element->ui/lib/transitions/${file}`;
});

externals = [Object.assign({
 vue: 'vue'
}, externals), nodeExternals()];

externals 可以防止將這些 import 的包打包到 bundle 中了袁,并在運(yùn)行時(shí)再去從外部獲取這些擴(kuò)展依賴朗恳。
舉例:
packages/table/src/table.vue:

import ElCheckbox from 'element-ui/packages/checkbox';
import { debounce, throttle } from 'throttle-debounce';
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
import Mousewheel from 'element-ui/src/directives/mousewheel';
import Locale from 'element-ui/src/mixins/locale';
import Migrating from 'element-ui/src/mixins/migrating';

按組件打包后,對(duì)應(yīng)的文件為lib/table.js

// EXTERNAL MODULE: ./packages/checkbox/index.js + 5 modules
var packages_checkbox = __webpack_require__(31);

// EXTERNAL MODULE: ./node_modules/_throttle-debounce@1.1.0@throttle-debounce/index.js
var _throttle_debounce_1_1_0_throttle_debounce = __webpack_require__(86);

// EXTERNAL MODULE: ./src/utils/resize-event.js
var resize_event = __webpack_require__(18);
......

由于external配置载绿,element-ui/packages/checkbox/index.js最后指向element-ui/lib/checkbox.js
我們來(lái)看一下打包后的 lib/table.js粥诫,我們可以看到編譯后的 table.js 對(duì) CheckBox 組件的依賴引入:

/***/ (function(module, exports) {

module.exports = require("throttle-debounce/debounce");
......

module.exports = require("element-ui/lib/checkbox");

這么處理的話,就不會(huì)打包生成 2 份 CheckBox JS 部分的代碼了崭庸,但是對(duì)于 CSS 部分怀浆,element-ui 并未處理冗余情況,可以看到 lib/theme-chalk/checkbox.csslib/theme-chalk/table.css 中都會(huì)有 CheckBox 組件的 CSS 樣式怕享。

其實(shí)执赡,要解決按需引入的 JS 和 CSS 的冗余問(wèn)題并非難事,可以用后編譯的思想函筋,即依賴包提供源碼沙合,而編譯交給應(yīng)用處理,這樣不僅不會(huì)有組件冗余代碼跌帐,甚至連編譯的冗余代碼都不會(huì)有首懈,實(shí)際上我們基于 element-ui fork 的組件庫(kù) zoom-ui 就應(yīng)用了后編譯技術(shù),之前在滴滴搞的開源組件庫(kù)cube-ui 組件庫(kù)也是這么玩的谨敛。更多后編譯相關(guān)介紹可以參考這篇文章究履。
iview UI組件,也可以使用babel-plugin-import插件佣盒,可以使import { Circle } from 'iview';挎袜,通過(guò)配置改成:
import _Table from "iview/src/components/table";// tables.js中直接引入table.vue
Vue.component("iCircle", _Circle);,
這種是相當(dāng)于直接引入編譯前的源碼,省去了按組件編譯的過(guò)程。

  • npm run build:utils
    設(shè)置BABEL_ENV=utils(.babelrc文件中env 選項(xiàng)的值將從 process.env.BABEL_ENV 獲取盯仪,如果沒(méi)有的話紊搪,則獲取 process.env.NODE_ENV 的值,它也無(wú)法獲取時(shí)會(huì)設(shè)置為 "development" )
    cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js:用Babel處理src下的directive全景、locale耀石、mixins、transitions和utils爸黄,并輸出到lib目錄
  • npm run build:umd
    利用babel-core及插件add-module-exportstransform-es2015-modules-umd處理src/locale/lang下的文件滞伟,生成umd格式的文件;
    利用file-save進(jìn)一步處理炕贵,如將define('zh-CN'處理成define('element/locale/zh-CN'梆奈,將global.zhCN = mod.exports處理成global.ELEMENT.lang = global.ELEMENT.lang || {};global.ELEMENT.lang.zhCN = mod.exports;
(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define('element/locale/zh-CN', ['module', 'exports'], factory);
  } else if (typeof exports !== "undefined") {
    factory(module, exports);
  } else {
    var mod = {
      exports: {}
    };
    factory(mod, mod.exports);
    global.ELEMENT.lang = global.ELEMENT.lang || {}; 
    global.ELEMENT.lang.zhCN = mod.exports;
  }
})(......
  • npm run build:theme,請(qǐng)見四称开、主題章節(jié)

1.1.2 pub

"pub": "
 npm run bootstrap &&
 sh build/git-release.sh &&
 sh build/release.sh &&
 node build/bin/gen-indices.js &&
 sh build/deploy-faas.sh
"
  • npm run bootstrap:
    安裝依賴亩钟,注意的是vue是以peerDependencies的形式配置的

  • sh build/git-release.sh:
    git checkout dev

  • sh build/release.sh:
    a.checkout master分支,并合并dev分支鳖轰;
    b.通過(guò)npx臨時(shí)安裝select-version-cli清酥,與開發(fā)者進(jìn)行交互,更新版本信息蕴侣;
    c.執(zhí)行npm run dist焰轻;
    d.測(cè)試ssr;
    e.進(jìn)入packages/theme-chalk目錄昆雀,利用npm version和npm publish辱志,發(fā)布主題(packages/theme-chalk是個(gè)基于gulp的工程),由此可見elementUI的主題是可以獨(dú)立發(fā)布的狞膘,不過(guò)會(huì)保證version跟elementUI保持一致荸频;
    f.退回到根目錄,提交代碼并通過(guò)npm version更新版本(更新package.json中的版本號(hào))客冈;
    g.在當(dāng)前分支(a步驟切換到master)push代碼旭从,然后checkout dev分支,并rebase master分支场仲,最后push代碼和悦;
    h.如果version為beta,則通過(guò)npm publis --tag打上標(biāo)簽渠缕,否則直接publish

  • node build/bin/gen-indices.js
    利用algoliasearch進(jìn)行搜索鸽素,需要把examples/docs/下的.md文件內(nèi)容以一定格式上傳給algolia

    element algolisearch

  • sh build/deploy-faas.sh

a.在build目錄下,新建temp_web目錄亦鳞;
b.執(zhí)行npm run deploy:build馍忽;
b1. npm run build:file:見前文棒坏,主要處理icon、生成src/index和國(guó)際化相關(guān)遭笋;
b2. webpack --config build/webpack.demo.js:見下文坝冕,主要用于生成或更新example目錄;
b3. echo element.eleme.io>>examples/element-ui/CNAME":examples/element-ui/CNAME文件中寫入element.eleme.ioManaging a custom domain for your GitHub Pages site
c.克隆elementgh-pages分支(可以通過(guò)http://elemefe.github.io/element/訪問(wèn)瓦呼,實(shí)際會(huì)根據(jù)CNAME文件的設(shè)置喂窟,路由到element.eleme.io,在這里進(jìn)行cname查詢)央串,并進(jìn)入element目錄磨澡;

d.根據(jù)版本號(hào)新建目錄,如2.13质和,然后將第b步中輸出目錄(examples/element-ui)里的內(nèi)容拷貝到新建目錄(2.13)里稳摄;
e.部署:faas deploy alpha -P element

1.1.3 dev

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

這塊主要功能是啟動(dòng)example,如下圖:


example

主要看build/webpack.demo.js饲宿,其除了通過(guò)設(shè)置入口文件entry.js(引入組件秩命、搭建網(wǎng)站)外,比較重要的兩點(diǎn)就是:
a.在examples/route.config.js中動(dòng)態(tài)生成如上圖展示的左側(cè)菜單褒傅,點(diǎn)擊不同組件名稱,加載對(duì)應(yīng)的examples/docs/*/下的對(duì)應(yīng)的組件markdown文件袄友;
b.而markdown文件就是用相關(guān)的npm工具殿托,對(duì)markdown文件進(jìn)行處理,生成上圖右側(cè)區(qū)域的內(nèi)容:

{
        test: /\.md$/,
        use: [
          {
            loader: 'vue-loader',
            options: {
              compilerOptions: {
                preserveWhitespace: false
              }
            }
          },
          {
            loader: path.resolve(__dirname, './md-loader/index.js')
          }
        ]
      }

markdown文件額外定制了特殊的語(yǔ)法或者標(biāo)記剧蚣,用于解析支竹,如:::demo、```html等鸠按。
markdown文件對(duì)應(yīng)的loader:

md-loader的原理很簡(jiǎn)單礼搁,輸入是文件的原始內(nèi)容,返回的是經(jīng)過(guò) loader 處理后的內(nèi)容目尖。對(duì)于 md-loader馒吴,輸入的是 .md 文檔,輸出的則是一個(gè) Vue SFC 格式的字符串瑟曲,這樣它的輸出就可以作為下一個(gè) vue-loader 的輸入做處理了饮戳。

我們來(lái)簡(jiǎn)單看一下 md-loader 中間處理過(guò)程。首先執(zhí)行了 md.render(source) 對(duì) md 文檔解析洞拨,提取文檔中 :::demo {content} ::: 內(nèi)容扯罐,分別生成一些 Vue 的模板字符串,然后再?gòu)倪@個(gè)模板字符串中循環(huán)查找 包裹的內(nèi)容烦衣,從中提取模板字符串到 output 中歹河,提取 script 到 componenetsString 中掩浙,然后構(gòu)造 pageScript,最后返回的內(nèi)容就是:

return
`
   <template>
     <section class="content element-doc">
       ${output.join('')}
     </section>
   </template>
   ${pageScript}
 `
;

最終生成的字符串滿足我們通常編寫的 .vue SFC 格式秸歧,它會(huì)作為下一個(gè) vue-loader 的輸入厨姚,所以這樣我們就相當(dāng)于通過(guò)加載一個(gè) .md 格式的文件的方式加載了 Vue 組件。

c.輸出目錄是examples/element-ui
關(guān)于examples詳細(xì)分析寥茫,后面進(jìn)行遣蚀。

1.1.4 test

通過(guò)karma測(cè)試工具和mocha, sinon-chai測(cè)試框架進(jìn)行單元測(cè)試

二、src分析

2.1 directives:mousewheel & repeat-click
2.2 locale(國(guó)際化)
elementUI——locale纱耻,國(guó)際化方案
2.3 mixins
elementUI——mixins
2.4 transitions
elementU——transitions
2.5 utils:其他分析文章里穿插介紹

三芭梯、組件

組件都放在packages目錄下,后面將陸續(xù)就寫的不錯(cuò)的組件進(jìn)行分析弄喘。

四玖喘、主題

elementUI——主題及自定義

五、examples分析

examples website

上圖各頁(yè)面實(shí)際是對(duì)應(yīng)著examples目錄蘑志,提供了指南說(shuō)明累奈、組件展示功能、主題定制急但、資源工具和語(yǔ)言切換功能澎媒。

推薦閱讀:
ElementUI的構(gòu)建流程
Element-UI 技術(shù)揭秘 - 組件庫(kù)的整體設(shè)計(jì)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市波桩,隨后出現(xiàn)的幾起案子戒努,更是在濱河造成了極大的恐慌,老刑警劉巖镐躲,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件储玫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡萤皂,警方通過(guò)查閱死者的電腦和手機(jī)撒穷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)裆熙,“玉大人端礼,你說(shuō)我怎么就攤上這事∪肼迹” “怎么了齐媒?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)纷跛。 經(jīng)常有香客問(wèn)我喻括,道長(zhǎng),這世上最難降的妖魔是什么贫奠? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任唬血,我火速辦了婚禮望蜡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拷恨。我一直安慰自己脖律,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布腕侄。 她就那樣靜靜地躺著小泉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冕杠。 梳的紋絲不亂的頭發(fā)上微姊,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音分预,去河邊找鬼兢交。 笑死,一個(gè)胖子當(dāng)著我的面吹牛笼痹,可吹牛的內(nèi)容都是我干的配喳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼凳干,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晴裹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起救赐,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涧团,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后净响,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喳瓣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年馋贤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畏陕。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡配乓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惠毁,到底是詐尸還是另有隱情犹芹,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布鞠绰,位于F島的核電站腰埂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蜈膨。R本人自食惡果不足惜屿笼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一牺荠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧驴一,春花似錦休雌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至胸懈,卻和暖如春担扑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背箫荡。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工魁亦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人羔挡。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓洁奈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親绞灼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子利术,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345