一、目錄結(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組件哈。