Element-UI(2.11.1) 構建流程

文章較長,建議收藏方便再看css

  1. Element是如何設計目錄?各目錄職能都是什麼?
  2. Element是如何利用webpack、nodejs搭建組件構建環境、構建工具?
  3. Element是如何打包樣式的?
  4. Element是如何實現全局引入、與按需引入的這兩個功能的?
  5. 主流的組件庫說明都是用markDown寫的,如何把md文檔,編譯成Vue組件?這方面E是如何配置的?
  6. Element如何完成國際化的?
  7. 若是是二次封裝,怎麼在Element的架構上,新建一個組件?

若是你對上面存在問題存在疑問,或者下面的內容,能夠幫助到你一點點。html

目錄分析


最外層目錄

├─build // 構建相關的nodejs腳本和webapck配置
├─examples // 展現Element組件的官網項目
├─lib // 打包出來的文件,發佈到npm包
├─packages // 組件代碼
├─src // 組件代碼公共方法
├─test // 測試代碼
├─Makefile // 構建工具文件
├─components.json // 組件列表
└─package.json // 記錄項目依賴包、script腳本
複製代碼

components.json


先了解一下最簡單的components.jsonvue

​ 這個文件記錄了你全部組件的路徑關係,最終以key,value的形式保存在這個文件中,方便後續導出,批量操做組件node

咱們能夠在不少地方應用了這個文件去參與Webpack、nodejs腳本的自動化構建流程,好比自動生成文件、自動生成多入口等等。。linux

// 爲了避免增長文章長度,只截取關鍵部分,完整請看源碼
{
  "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",
  "input-number": "./packages/input-number/index.js",
  "radio": "./packages/radio/index.js",
  "radio-group": "./packages/radio-group/index.js",
  "radio-button": "./packages/radio-button/index.js",
  "checkbox": "./packages/checkbox/index.js",
}

複製代碼

Packages目錄、src目錄


一個組件庫,最重要的是組件庫的源碼。webpack

ElementUI的組件庫源碼主要存在於packagessrc兩個目錄下面。git

Packages文件夾


// packages文件夾 每一個組件都有本身的文件夾
// 爲了避免增長文章長度,只截取關鍵部分,完整請看源碼
├── alert
├── aside
├── button
├── button-group
├── calendar
├── checkbox
├── form
├── table
├── theme-chalk // 組件的樣式都在這裏 index.scss 裏面包含了全部的樣式文件
複製代碼
Button文件夾

packages下,每一個組件都是用一個獨立的文件夾去管理github

. // packages/button文件夾
├── index.js // 組件入口文件
└── src
    ├── button-group.vue // button-group源碼
    └── button.vue // button源碼
複製代碼
// packages/button/index.js
// 引入組件button組件
import ElButtonfrom './src/button';

// 爲組件提供install方法,讓Vue能夠經過Vue.use(Button)形式去使用
ElButton.install = function(Vue) {
  Vue.component(ElButton.name, ElButton);
};

export default ElButton;

複製代碼
theme-chalk 文件夾

theme-chalk文件夾是用來管理全部組件的樣式的,文件夾大體結構以下:web

// 爲了避免增長文章長度,只截取關鍵部分,完整請看源碼
├── packages // 組件入口文件
└── theme-chalk
    ├── alert.scss // alert組件的樣式
    └── button.scss // button組件的樣式
    └── aside.scss
    └── checkbox.scss
    └── index.scss // 該文件引入了全部樣式,是總的樣式入口文件
    └── gulpfile.js // 樣式的編譯、壓縮、輸出是經過gulp工做流來完成的
複製代碼

這裏比較有趣的是,能夠看到Element對樣式的打包、編譯、處理、輸入,是經過gulp來完成的,而不是Webpack。vue-router

另外還有一個比較流行的組件庫IView,也是經過這種gulp工做流的形式去處理的樣式

Src文件夾


packages是把每一個組件單獨分開去管理的

Src的做用就是,把packages的全部公共方法、自定義指令、組件國際化都放在這裏

每種不一樣類型的公共方法的,都存在不一樣的文件夾中

// src目錄
├── directives // 項目封裝好的Vue自定義指令
├── index.js // 組件的總體入口文件
├── locale // 組件的國際化內容
├── mixins // 一些組件的mixins
├── transitions // 動畫的封裝
└── utils // 工具函數集合
複製代碼

這裏,咱們重點關注一下index.js

// 爲了避免增長文章長度,只截取關鍵部分,完整請看源碼

/* Automatically generated by './build/bin/build-entry.js' */

// 一、導入了全部組件packages下的全部組件
import Pagination from '../packages/pagination/index.js';
import Dialog from '../packages/dialog/index.js';
import Autocomplete from '../packages/autocomplete/index.js';
import Dropdown from '../packages/dropdown/index.js';

const components = [
  Pagination,
  Dialog,
  Autocomplete,
  Dropdown,
  DropdownMenu,
  DropdownItem,
];

// 二、提供了install方法,幫咱們掛載了一些組件與變量
const install = function(Vue, opts = {}) {
  locale.use(opts.locale);
  locale.i18n(opts.i18n);
  // 把全部的組件註冊到Vue上面
  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;

};

// 導出install方法,能夠理解爲一個插件、以及一些功能好比國際化功能
export default {
  install,
  locale
};

複製代碼

在這個文檔中,咱們能夠看到這個文件的最開頭有這麼一句話:

Automatically generated by './build/bin/build-entry.js'

能夠知道這是經過build-entry腳本去生成的,後續咱們看項目的構建腳本的時候會簡單分析如何實現,這裏簡單瞭解一下便可。

另外這個自動生成的index.js文件,主要的實現的功能是如下三點:

  1. 導入了全部組件packages下的全部組件
  2. 對外暴露了install方法,讓使用者經過Vue.use去使用的時候,自動執行一些自定義操做,好比掛載$loading、​$Msgbox等變量
  3. 導出install方法、一些公共的變量

最後,由於這裏由於引入了全部的組件源碼,因此這也是一個Webpack的打包的入口文件,在看webpack配置的時候會具體說。

examples目錄


Examples,至關於一個獨立的Vue項目,主要的功能是展現文檔。

Element的官網展現的內容,就是在這個examples目錄。

這裏文件夾,咱們介紹一下比較主要的文件

├── app.vue // 根組件
├── components // 項目公共組件
├── docs // 每個頁面的組件都存在在這
├── entry.js  // 項目入口
├── route.config.js // 路由配置,經過腳本自動生成而來
複製代碼

entry.js 項目入口文件

咱們能夠看到這個文件中

經過這代碼import Element from 'main/index.js',把以前全部寫的組件js都引入了進來。

這裏配置了Webpack的aliasmain/index.js,其實就是咱們上面提到的src/index.js文件

另外還引入了全局的css import 'packages/theme-chalk/src/index.scss';

import Vue from 'vue';
import entry from './app';
import VueRouter from 'vue-router';
import Element from 'main/index.js';
import hljs from 'highlight.js';
import routes from './route.config';
import demoBlock from './components/demo-block';
import MainFooter from './components/footer';
import MainHeader from './components/header';
import SideNav from './components/side-nav';
import FooterNav from './components/footer-nav';
import title from './i18n/title';

import 'packages/theme-chalk/src/index.scss';
import './demo-styles/index.scss';
import './assets/styles/common.css';
import './assets/styles/fonts/style.css';
import icon from './icon.json';

Vue.use(Element);
Vue.use(VueRouter);
Vue.component('demo-block', demoBlock);
Vue.component('main-footer', MainFooter);
Vue.component('main-header', MainHeader);
Vue.component('side-nav', SideNav);
Vue.component('footer-nav', FooterNav);

const globalEle = new Vue({
  data: { $isEle: false } // 是否 ele 用戶
});

Vue.mixin({
  computed: {
    $isEle: {
      get: () => (globalEle.$data.$isEle),
      set: (data) => {globalEle.$data.$isEle = data;}
    }
  }
});

Vue.prototype.$icon = icon; // Icon 列表頁用

console.log(routes);
const router = new VueRouter({
  mode: 'hash',
  base: __dirname,
  routes
});

router.afterEach(route => {
  // https://github.com/highlightjs/highlight.js/issues/909#issuecomment-131686186
  Vue.nextTick(() => {
    const blocks = document.querySelectorAll('pre code:not(.hljs)');
    Array.prototype.forEach.call(blocks, hljs.highlightBlock);
  });
  const data = title[route.meta.lang];
  for (let val in data) {
    if (new RegExp('^' + val, 'g').test(route.name)) {
      document.title = data[val];
      return;
    }
  }
  document.title = 'Element';
  ga('send', 'event', 'PageView', route.name);
});

new Vue({ // eslint-disable-line
  ...entry,
  router
}).$mount('#app');

複製代碼

docs文件夾

Element官網支持4種語言,docs一共有4個文件夾,每一個文件夾裏面的內容基本是同樣的,只是根據語言不一樣,翻譯一下。

這裏咱們重點關注zh-CN文件下的中文文檔

咱們能夠看到裏面所有都是md文檔,而每個md文檔,分別對應着其官網的展現頁面

用markdown文檔編寫展現文檔,其實這是各大組件庫寫展現文檔的一個主流方式

可是用這種文檔展現,須要咱們去克服一個技術上的難題

如何把md文檔上寫的Vue代碼,編譯成Vue組件呢?

這裏咱們先把問題拋出來,不做回答,讓你們帶着問題看下去。

咱們先看button.md,就是官網展現Button組件的時候顯示的內容

該文件裏其實就是一些普通的md語法。

咱們聚焦到上圖中,紅色圈圈裏的代碼

它是用了特定的標識符給包裹起來,好比:::demo:::就把一些Vue組件的代碼給包裹起來

:::demo

\```html 這裏面的內容有兩個做用: 一、以Vue組件的形式展示出來 二、以md的形式展示出來 \``` ::: 複製代碼

爲何要同:::demo這樣的標識符去給代碼包裹起來呢?

實際上是由於,Element要把demo標識符裏面的代碼給單獨提取出去,去給Vue-loader處理,從而生成Vue組件。

那麼具體實施思路是怎樣的呢?

在說這個以前,先複習一下Webpack中的loaders相關的三個知識

  • loaders的執行順序是,從下到上、從右到左的
  • 前一個loaders執行的結果,會返回到下一個loaders中
  • Vue-loader能夠幫咱們把SFC規範的語法,編譯成Vue組件

這個功能的實現,底層就是Element本身編寫了一個loader,把裏面的內容抽出來,而後傳給vue-loader去編譯。

咱們先看看其webpack配置

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

能夠看到,當遇到*.md文檔的時候

首先會用./md-loader/index.js,去處理。

而後把處理的結果,再傳給vue-loader.

最終Vue-loader就幫咱們編譯成Vue組件

那麼./md-loader/index.js作了什麼呢?

總結來講,就是這個loader最終返回的是一段字符串

這段字符串就是符合Vue SFC 規範的字符串

那麼這一串字符串,就能夠被vue-loader去編譯,爲咱們生成Vue組件。

因此一個一個單獨的md文檔,經過路由+webpack的配置,其實跟一個個單獨的Vue組件是差很少的,可是同時卻提供了咱們寫md文檔的方便

當咱們跑起來項目的時候,修改這裏的md文檔,是會實時更新的。

咱們試試題目修改爲 Ynet-button,那麼在官方就能夠看到

package.json


其實不少人都說,若是要理解一個項目的概要,首先要看的就是package.json

它記錄了項目的依賴以及腳本,讓你理解項目的一些主要功能點,咱們來看看它的大概樣式

// 爲了避免增長文章長度,只截取關鍵部分,完整請看源碼
{
  "name": "element-ui",
  "version": "2.11.1",
  "description": "A Component Library for Vue.js.",
  // 發包上去後,項目的入口文件 
  // import Element from 'element-ui' 時候引入的就是main中的文件
  "main": "lib/element-ui.common.js",// element-ui.common.js是commonsjs規範 lib/index.js是umd規範
 // 當你npm publish 發包的時候,發什麼到Npm上,由files決定,功能和.gitignore相似
  "script": {}, // webpack配置,nodejs腳本,這部分單獨在下文中介紹
  "files": [
    "lib",
    "src",
    "packages",
    "types"
  ],
  // TypeScript 入口文件
  "typings": "types/index.d.ts",
  // 倉庫信息
  "repository": {
    "type": "git",
    "url": "git@github.com:ElemeFE/element.git"
  },
  // 項目主頁的URL地址
  "homepage": "http://element.eleme.io",
  "keywords": [
    "eleme",
    "vue",
    "components"
  ],
  // 使用MIT協議,具體見參考資料
  "license": "MIT",
  // 提issuse、bugs的地方
  "bugs": {
    "url": "https://github.com/ElemeFE/element/issues"
  },
  // unpkg.com是一個源自 npm 的全球快速 CDN
  // 若是經過unpkg.com這種CDN方式引入庫的話,默認文件路徑
  // 官方文檔在CDN引入的時候有提 https://element.eleme.cn/#/zh-CN/component/installation
  // lib/index.js是umd規範,由webpack.conf.js生成
  "unpkg": "lib/index.js",
  // 聲明當前模塊包含 style 部分,並指定入口文件。
  "style": "lib/theme-chalk/index.css",
  "dependencies": {
  },
  // 解決模塊之間相互依賴的問題,具體見參考資料
  "peerDependencies": {
    "vue": "^2.5.17"
  },
  "devDependencies": {
  }
}
複製代碼
script腳本彙總

package.json中,script腳本的理解對我來講難度是比較大的,因此這裏單獨拎出來去說。

{
"scripts": {
  // 安裝依賴,官方推薦優先用yarn
    "bootstrap": "yarn || npm i",
  // build/bin/iconInit.js 解析icon.scss。把全部的icon的名字放在icon.json裏。最後掛到Vue原型上的$icon的上,方便循環出來.具體使用見【補充資料】
  // build/bin/build-entry.js 根據components.json,生成src/index.js文件,核心就是json-templater/string插件的使用【插件使用見參考資料】
  // build/bin/i18n.js 根據examples/i18n/page.json和模板,生成不一樣語言的demo
	//  build/bin/version.js 根據package.json中的versions,生成examples/versions.json
    "build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
  // build/bin/gen-cssfile // 根據components.json,生成生成package/theme-chalk/index.scss
  // gulp build --gulpfile packages/theme-chalk/gulpfile.js 用gulp構建工具,編譯scss、壓縮、輸出css
  // cp-cli 是一個跨平臺的copy工具,和CopyWebpackPlugin相似、linux的cp差很少(不具備跨平臺的功能),
    "build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
  // 把src目錄下的除了index.js入口文件外的其餘文件經過babel轉譯,而後移動到lib文件夾下。
    "build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
// 跑起來demo
    "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",
 // 總體構建,打包出lib庫
    "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",      
  // 生成umd模塊的語言包
    "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 bui
  // 對項目進行eslint檢測
    "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",
複製代碼
Script 腳本的進一步剖析

npm run bootstrap` 安裝依賴,推薦優先用yarn
npm run build:file ` 用腳本自動化生成一些文件

該命令跑了4個任務

`node build/bin/iconInit.js`  // 解析icon.scss。把全部的icon的名字放在icon.json裏。最後掛到Vue原型上的$icon的上
& `node build/bin/build-entry.js` // 根據components.json,生成src/index.js文件,核心就是json-templater/string插件的使用【插件使用見參考資料】
& `node build/bin/i18n.js` // 根據examples/i18n/page.json和模板,生成不一樣語言的demo,當選擇語言不同,會跑不一樣語言的文件夾。
& `node build/bin/version.js` // 根據package.json中的versions,生成examples/versions.json,而後在header中獲取這個versions.json來使用
複製代碼

iconInit.js

這文件是爲了把icon.scss裏面全部的icon名字,抽離出來,而後存放在 examples/icon.json文件上。

但是Element爲何要把icon的名字提取出來呢?

咱們看一下,最終展現在官網上的demo就知道了

由於Element中Icon有太多了,一個一個去寫就太蠢了,因此才把全部icon都存在一個數組裏,而後經過v-for循環出來

icon.json

這個文件,咱們能夠看到最終抽離出來的效果

最後使用,咱們能夠看到入口文件entry.js、與icon.md中看到做者這麼作的最終使用狀況

// entry.js
import Vue from 'vue';
import icon from './icon.json';
Vue.prototype.$icon = icon; // Icon 列表頁用
------------------------------------------------------------------

// icon.md
### 圖標集合
<ul class="icon-list">
  // 直接把全部的icon循環出來
  <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>
複製代碼

build-entry.js

在上面介紹src/index.js的時候,咱們知道index.js文件,是經過這個腳本去自動化構建生成的,它究竟是如何實現的呢?

這裏咱們結合源碼來看:

// 把全部組件的依賴關係,引入進來
var Components = require('../../components.json');
var fs = require('fs');

// https://www.npmjs.com/package/json-templater 可讓string與變量結合,輸出一些內容
var render = require('json-templater/string');

// https://github.com/SamVerschueren/uppercamelcase 轉化爲駝峯 foo-bar >> FooBar
var uppercamelcase = require('uppercamelcase');

var path = require('path');

var endOfLine = require('os').EOL;// os.EOL屬性是一個常量,返回當前操做系統的換行符(Windows系統是\r\n,其餘系統是\n)

// 生成文件的名字和路徑
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 => {
  // menu-item 轉化爲 MenuItem
  var componentName = uppercamelcase(name);
  // IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
  includeComponentTemplate.push(render(IMPORT_TEMPLATE, {
    name: componentName,
    package: name
  }));
  // 獲得的數據結構是: includeComponentTemplate = ['import MenuItem from \'../packages/menu-item/index.js\';', ....]

  if (['Loading', 'MessageBox', 'Notification', 'Message', 'InfiniteScroll'].indexOf(componentName) === -1) {
    // INSTALL_COMPONENT_TEMPLATE = ' {{name}}';
    installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, {
      name: componentName,
      component: name
    }));
  }
  // 獲得的數據結構是: installTemplate = ['Pagination', 'Dialog', ...]

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

// 這裏若是理解include、install、version、list分別表明什麼,下面的代碼就容易理解了
// 其實就是用json-templater庫,把 MAIN_TEMPLATE 中的字符串,和include、install、version、list這些變量結合出來,最終的樣式就是src/index.js的樣式
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)
});

// 結果輸出到src/index.js中
fs.writeFileSync(OUTPUT_PATH, template);

複製代碼

大概實現邏輯就是,根據components.json,生成src/index.js文件

核心就是json-templater/string插件的使用【插件使用見參考資料】

build/bin/i18n.js

Element項目的國際化,分爲兩個部分,分別用兩種不一樣的方式去處理。

第一部分是,官網的Demo展現國際化

第二部分是,組件源碼國際化

build/bin/i18n.js是幫咱們完成第一部分的國際化工做的

第一部分:Element的展現的官網是如何實現國際化的

首先官網的主頁(index)頁面的國際化,是以examples/pages/template爲公共的模板,再genuine不一樣的語言,分別生成不一樣的文件

咱們能夠看看template裏有什麼

裏面是一個個.tpl文件,每個文件都是一個模板

而每一個tpl文件,都是符合SFC規範的的Vue文件格式。

在tpl文件中,須要國際化的地方,會根據必定的格式,用數字標識出來

// guide.tpl
<script>
  export default {
    data() {
      return {
        lang: this.$route.meta.lang,
        navsData: [
          {
            path: '/design',
            name: '<%= 1 >' // 國際化標誌位
          },
          {
            path: '/nav',
            name: '<%= 2 >'// 國際化標誌位
          }
        ]
      };
    }
  };
</script>
複製代碼

而後根據不一樣的語言,把examples/i18n/page.json(這個文件包含首頁全部的國際化字段)裏,提早寫好的字段,按照標誌位填充進去

最終生成的這一套組件,組合起來就是就是Element展現的首頁

這個首頁是支持多語言的,每一種語言都有對應的一個vue文件。

那麼它是如何作到不一樣的語言,有不一樣的vue文件呢?

build/bin/i18n.js,就是幫咱們完成這個事項。

大概的工做流程就是,引入examples/i18n/page.json,而後循環,根據不一樣的數據結構

把tpl文件的標誌標誌位,經過正則匹配出來,並替換成本身預先設定好的字段

// 把標誌位地方的字段,替換成page.json裏預先寫好的字段
content = content.replace(new RegExp(`<%=\\s*${ key }\\s*>`, 'g'), pairs[key]);
// 最後再輸出
fs.writeFileSync(outputPath, content);
複製代碼

最終看到的結果就是examples裏面的內容

這樣就完成了官網首頁的國際化,那麼markdown文檔的國際化呢?

個人答案是,本身翻譯的,這部分是沒有用自動化流程去構建,只是用腳本爲咱們生成一個空的md文檔,內容由咱們本身去編寫。(若是錯了,請指正)

第二部分:組件源碼的國際化

packages/color-picker/src/components/picker-dropdown.vue 中,咱們在模板部分能夠看到這個語言包的使用:

<el-button
  size="mini"
  type="text"
  class="el-color-dropdown__link-btn"
  @click="$emit('clear')">
  {{ t('el.colorpicker.clear') }}
</el-button>
<el-button
  plain
  size="mini"
  class="el-color-dropdown__btn"
  @click="confirmValue">
  {{ t('el.colorpicker.confirm') }}
</el-button>

複製代碼

這個t('el.colorpicker.clear')返回的是什麼內容呢?

咱們再src/locale/lang下面能夠看到

export default {
  el: {
    colorpicker: {
      confirm: '肯定',
      clear: '清空'
    },
  }
複製代碼

因此t函數,實際上會根據你的語言,去獲取不一樣的語言包,而後再去加載去對於的字段出來,這一點跟普通項目的國際化沒什麼區別。

可是Element是如何取構建t函數的呢?是經過什麼手段幫t綁定到Vue上面呢

這部分的操做是在src/locale/lang/index.js完成的

若是你沒有設置語言包,在index.js裏面就會默認使用中文

npm run build:theme

這個命令幫咱們跑了3個命令

`node build/bin/gen-cssfile` // 根據components.json,生成生成package/theme-chalk/index.scss,把全部組件的樣式都導入到index.scss
&& `gulp build --gulpfile packages/theme-chalk/gulpfile.js` // 用gulp構建工具,編譯scss、壓縮、輸出css
&& `cp-cli packages/theme-chalk/lib lib/theme-chalk` //cp-cli 是一個跨平臺的copy工具,和CopyWebpackPlugin相似、linux的cp差很少(不具備跨平臺的功能) 把文件複製到lib/theme-chalk下
複製代碼

首先咱們知道ElemntUI在使用的時候,是提供2種方式的。

一種是全局引入、一種是按需引入。

因此在樣式的處理上,也有要兩種處理方法,分別用於全局引入、按需引入的

咱們先看看兩種引入方式的具體使用

全局引入

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)
});
複製代碼

按需引入

import Vue from 'vue';
import { Button, Select } from 'element-ui';
// 上面這句會經過babel-plugin-component插件編譯爲
// var button = require('element-ui/lib/button')
// require('element-ui/lib/theme-chalk/button.css')
// 如何轉義請看配置 https://element.eleme.cn/#/zh-CN/component/quickstart
import App from './App.vue';

Vue.use(Button)
Vue.use(Select)

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

gulpfile.js 實現樣式打包


既然有兩種引入方式,那麼Element在打包組件的樣式,就必須打包兩種方案。

還記得咱們上面介紹的packages/theme-chalk嗎?

其實只要把packages/theme-chalk下的全部scss都編譯成css就能夠了

當你須要全局引入的時候,就去引入index.scss文件,這樣全部組件的樣式都齊全了。

若是你想按需引入,好比按需引入button組件,那麼只要引入button.scss文件,就能夠了。

那麼如何把packages/theme-chalk下的全部scss都編譯成css?(Element是如何打包樣式的?)

這一點webpack不容易辦到(鑑於我使用webpack的渣渣經驗,我沒想到該如何用webpack去實現會比較優雅,若是小夥伴知道,歡迎分享),可是gulp卻很容易

因此ElementUI的樣式打包,並非用webpack的,是用了gulp,基於工做流去處理樣式。

咱們看看Element如何結合gulp去使用

'use strict';

const { series, src, dest } = require('gulp');
const sass = require('gulp-sass'); // 編譯gulp工具
const autoprefixer = require('gulp-autoprefixer');// 添加廠商名字工具
const cssmin = require('gulp-cssmin'); // 壓縮css工具

function compile() {
  return src('./src/*.scss') // 引入src下全部的scss樣式
    .pipe(sass.sync()) // 把scss文件編譯成css
    .pipe(autoprefixer({ // 基於目標瀏覽器版本,添加廠商前綴
      browsers: ['ie > 9', 'last 2 versions'],
      cascade: false
    }))
    .pipe(cssmin()) // 壓縮css
    .pipe(dest('./lib')); // 輸出到lib下
}

function copyfont() {
  return src('./src/fonts/**') // 讀取src/fonts下的全部文件
    .pipe(cssmin()) // 壓縮
    .pipe(dest('./lib/fonts')); // 輸出到lib/fonts下
}

exports.build = series(compile, copyfont);

複製代碼

最終就會幫咱們打包出了樣式文件。

gen-cssfile.js

根據components.json,生成生成package/theme-chalk/index.scss,把全部組件的樣式都導入到index.scss

這樣每次新增組件,就不用手動去引入新增組建的樣式了

cp-cli packages/theme-chalk/lib lib/theme-chalk

cp-cli 是一個跨平臺的copy工具,和CopyWebpackPlugin相似、linux的cp差很少(不具備跨平臺的功能) 把文件複製到lib/theme-chalk下

Webpack配置


咱們能夠看到E的webpack配置主要要由下面5中配置去分工

webpack.common.js

生成commonjs2規範的入口文件 /lib/element-ui.common.js,用於import的方法引入

webpack.component.js

把各組件以commonjs規範單獨打包出來,提供按需引入的功能

webpack.conf.js

生成umd規範的入口文件/lib/index.js,用於CDN方法引入

webpack.test.js

webpack.test.js 跑單元測試的,這裏咱們忽略。

webpack.demo.js

用於跑Element官網的基礎配置

makeFile


Element中還用了makefile爲咱們編寫了一些額外的腳本

當咱們在命令行中輸入 make時,能夠看到

這是已經幫咱們編寫好的腳本

這裏重點說一下 make new <component-name> [中文] 這個命令

這是用來建立一個新的組件的,而後這個命令會幫咱們基於新建好的組件,幫咱們進行一系列的自動化操做

當運行這個命令的時候,其實運行的是 node build/bin/new.js

new.js腳本主要作了下面幾件事:

  1. 把你新建的組件添加到 components.json
  2. 添加到 index.scss
  3. 添加到 element-ui.d.ts
  4. 建立 package
  5. 添加到 nav.config.json

因此當咱們要在Elment架構上,新建一個elect-bill組件

只要執行 make new elect-bill 電子帳號,而後不用配置,就能夠直接去開發了

補充資料


  • MIT協議
preview

參考連接

相關文章
相關標籤/搜索