說(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)及工程化
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
病梢、pub
、dev
和test
命令
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.json
和examples/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.css
和lib/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-exports
和transform-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 devsh 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
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.io
:Managing a custom domain for your GitHub Pages site
c.克隆element的gh-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,如下圖:
主要看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)行分析弄喘。
四玖喘、主題
五、examples分析
上圖各頁(yè)面實(shí)際是對(duì)應(yīng)著examples目錄蘑志,提供了
指南
說(shuō)明累奈、組件
展示功能、主題
定制急但、資源
工具和語(yǔ)言切換
功能澎媒。
推薦閱讀:
ElementUI的構(gòu)建流程
Element-UI 技術(shù)揭秘 - 組件庫(kù)的整體設(shè)計(jì)