elementUI源碼分析-01-目錄及整體介紹

一、目錄結(jié)構(gòu)

build:webpack等打包相關(guān)的文件
examples:官網(wǎng)示例等
packages:組件相關(guān)的核心代碼
src/directives:封裝的自定義指令
src/locale: 語言相關(guān)的
src/mixins:方式mixin相關(guān)的
src/transitions: 封裝的相關(guān)動(dòng)畫
src/utils: 相關(guān)的工具函數(shù)
test:單元測試文件
types:ts相關(guān)的文件

二、使用配置

當(dāng)我們在vue中使用element-ui的時(shí)候,一般會(huì)有如下代碼:

import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';

Vue.use(ElementUI);

new Vue({
  el: '#app',
  render: h => h(App)
});

以上代碼便完成了 Element 的引入,需要注意的是css的文件需要單獨(dú)引入。

我們知道vue的插件開發(fā)形式都是調(diào)用Vue.use()進(jìn)行注冊的,Vue.use()會(huì)調(diào)用傳入對(duì)象的install方法。

所以當(dāng)我們調(diào)用Vue.use(ElementUI)注冊element-ui的時(shí)候,會(huì)調(diào)用ElementUI的install方法,那我們看看ElementUI.install做了什么呢?

ElementUI.install這個(gè)方法在src/index.js中

// 引入支持的組件
import Pagination from '../packages/pagination/index.js';
import Dialog from '../packages/dialog/index.js';
...

// 定義組件
const components = [
  Pagination,
  Dialog,
    ...
];

// 執(zhí)行Vue.use(ElementUI)的調(diào)用的install方法
const install = function(Vue, opts = {}) {
    <!-- 默認(rèn)接受第一個(gè)參數(shù)vue實(shí)例,opts為Vue.use傳進(jìn)來的第二個(gè)參數(shù) -->
  locale.use(opts.locale);
  locale.i18n(opts.i18n);
    // 循環(huán)注冊所有組件
  components.forEach(component => {
    Vue.component(component.name, component);
  });

  Vue.use(InfiniteScroll);
  Vue.use(Loading.directive);
// 在vue的原型上添加方法
  Vue.prototype.$ELEMENT = {
    size: opts.size || '',
    zIndex: opts.zIndex || 2000
  };

  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;

};

/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}
// 暴露出去的對(duì)象
export default {
  version: '2.14.1',
  locale: locale.use,
  i18n: locale.i18n,
  install,
  CollapseTransition,
  Loading,
    ...
};

所以,當(dāng)注冊ele的時(shí)候Vue.use(ElementUI),會(huì)調(diào)用ElementUI暴露的install方法,install內(nèi)部接受了Vue實(shí)例,且用Vue.component(component.name, component)全局注冊了ele提供的所有組件。這樣我們在項(xiàng)目里就可以使用ele提供的組件。

其實(shí)index.js這個(gè)文件,是ele在打包的時(shí)候自動(dòng)生成的,下面我們來看下ele的自動(dòng)化配置。

三、自動(dòng)化配置

先看下package.json下的構(gòu)建命令

  "scripts": {
    "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",
    "build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
    "build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
    "build:umd": "node build/bin/build-locale.js",
    "clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
    "deploy:build": "npm run build:file && cross-env NODE_ENV=production webpack --config build/webpack.demo.js && echo element.eleme.io>>examples/element-ui/CNAME",
    "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",
    "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",
    "dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js",
    "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",
    "i18n": "node build/bin/i18n.js",
    "lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet",
    "pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js && sh build/deploy-faas.sh",
    "test": "npm run lint && npm run build:theme && cross-env CI_ENV=/dev/ BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
    "test:watch": "npm run build:theme && cross-env BABEL_ENV=test karma start test/unit/karma.conf.js"
  },

我們以npm run dev為例看一下是如何進(jìn)行構(gòu)建的

首先當(dāng)我們運(yùn)行npm run dev 的時(shí)候,運(yùn)行的命令如下

"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",

分解下命令

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

npm run bootstrap 即 yarn || npm i,幫我們安裝了整個(gè)項(xiàng)目需要的依賴、

npm run 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 實(shí)際上是node運(yùn)行了這個(gè)腳本,那我們?nèi)タ纯催@個(gè)腳本下寫了什么。

var postcss = require('postcss');
var fs = require('fs');
var path = require('path');
// 讀取icon.scss文件
var fontFile = fs.readFileSync(path.resolve(__dirname, '../../packages/theme-chalk/src/icon.scss'), 'utf8');
var nodes = postcss.parse(fontFile).nodes;
var classList = [];
// 循環(huán)遍歷通過pstcss獲取到的cssNode,獲取到class
nodes.forEach((node) => {
  var selector = node.selector || '';
  var reg = new RegExp(/\.el-icon-([^:]+):before/);
  var arr = selector.match(reg);

  if (arr && arr[1]) {
    classList.push(arr[1]);
  }
});

classList.reverse(); // 希望按 css 文件順序倒序排列

fs.writeFile(path.resolve(__dirname, '../../examples/icon.json'), JSON.stringify(classList), () => {});

這個(gè)文件其實(shí)就是讀取theme-chalk/src/icon.scss文件的class名,生成了一個(gè)數(shù)組,并將這個(gè)數(shù)組寫入到examples/icon.json,這個(gè)文件會(huì)在渲染圖標(biāo)組件的時(shí)候用到

node build/bin/build-entry.js

這個(gè)文件代碼如下

var Components = require('../../components.json');
var fs = require('fs');
var render = require('json-templater/string');
var uppercamelcase = require('uppercamelcase');
var path = require('path');
var endOfLine = require('os').EOL;

var OUTPUT_PATH = path.join(__dirname, '../../src/index.js');
var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
var INSTALL_COMPONENT_TEMPLATE = '  {{name}}';
var MAIN_TEMPLATE = `/* Automatically generated by './build/bin/build-entry.js' */

{{include}}
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';

const components = [
{{install}},
  CollapseTransition
];

const install = function(Vue, opts = {}) {
  locale.use(opts.locale);
  locale.i18n(opts.i18n);

  components.forEach(component => {
    Vue.component(component.name, component);
  });

  Vue.use(InfiniteScroll);
  Vue.use(Loading.directive);

  Vue.prototype.$ELEMENT = {
    size: opts.size || '',
    zIndex: opts.zIndex || 2000
  };

  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;

};

/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}

export default {
  version: '{{version}}',
  locale: locale.use,
  i18n: locale.i18n,
  install,
  CollapseTransition,
  Loading,
{{list}}
};
`;

delete Components.font;

var ComponentNames = Object.keys(Components);

var includeComponentTemplate = [];
var installTemplate = [];
var listTemplate = [];

ComponentNames.forEach(name => {
  var componentName = uppercamelcase(name);

  includeComponentTemplate.push(render(IMPORT_TEMPLATE, {
    name: componentName,
    package: name
  }));

  if (['Loading', 'MessageBox', 'Notification', 'Message', 'InfiniteScroll'].indexOf(componentName) === -1) {
    installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, {
      name: componentName,
      component: name
    }));
  }

  if (componentName !== 'Loading') listTemplate.push(`  ${componentName}`);
});

var template = render(MAIN_TEMPLATE, {
  include: includeComponentTemplate.join(endOfLine),
  install: installTemplate.join(',' + endOfLine),
  version: process.env.VERSION || require('../../package.json').version,
  list: listTemplate.join(',' + endOfLine)
});

fs.writeFileSync(OUTPUT_PATH, template);
console.log('[build entry] DONE:', OUTPUT_PATH);

這個(gè)文件主要是用來生成src/index.js文件的,首先讀取了components.json(ele提供的所有組件的配置文件),讀取配置后再結(jié)合json-templater/string,將其依賴注入到MAIN_TEMPLATE這個(gè)變量中,其中雙大括號(hào)里面的就是變量,最終生成了index.js。

node build/bin/i18n.js & node build/bin/version.js

這兩個(gè)文件也不難閱讀,一個(gè)是用來根據(jù)page.json的語言生成不同語言版本的官網(wǎng),一個(gè)用來動(dòng)態(tài)生成版本號(hào)的。

那么整體梳理下來,整個(gè)過程就是,首先我們把代碼clone下來,然后當(dāng)我們輸入npm run dev 運(yùn)行項(xiàng)目的時(shí)候,會(huì)先去install裝全部依賴,然后再初始化圖標(biāo)配置文件,動(dòng)態(tài)生成index.js入口文件,根據(jù)語言動(dòng)態(tài)生成官網(wǎng)模板,再生成選擇版本號(hào)的配置文件。一切準(zhǔn)備就緒后再運(yùn)行webpack,配置文件為webpack.demo.js,生成不同語言的官網(wǎng)。

感覺整個(gè)過程全部執(zhí)行腳本完成,還是很自動(dòng)化的,值得學(xué)習(xí)。

至于其他運(yùn)行方式得執(zhí)行結(jié)果,大家可以自行根據(jù)配置解讀,畢竟跟重要的是閱讀如何寫vue組件哈。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容