element源碼學習一 —— 認識架構

來源:互聯網
上載者:User

由於面試需要,先來幾發 element 源碼學習部落格。Vue 源碼還將繼續更新。

好,現在我們開始學習 element —— 最受歡迎的 Vue 使用者介面架構。 package.json

我覺得要看一個前端項目,首先必須得看看 package.json 這個檔案。 編譯入口

來看看編譯的入口

  "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",    # 構建umd    "build:umd": "node build/bin/build-locale.js"  }

這裡我們以看源碼的角度,先瞭解構建檔案命令。其實就是 node 執行了幾個 js 指令碼。我們深入看下 iconInit、 build-entry、 i18n、 version 這些指令檔。

// build/bin/build-all.js'use strict';const components = require('../../components.json');const execSync = require('child_process').execSync;const existsSync = require('fs').existsSync;const path = require('path');let componentPaths = [];delete components.index;delete components.font;// 遍曆 components 的 key,找到相應 component 的路徑,將路徑儲存到 componentPaths 數組Object.keys(components).forEach(key => {  const filePath = path.join(__dirname, `../../packages/${key}/cooking.conf.js`);  if (existsSync(filePath)) {    componentPaths.push(`packages/${key}/cooking.conf.js`);  }});// pathA,pathB,pathCconst paths = componentPaths.join(',');// 拼接為 shell 命令,並調用 execSync 方法執行。const cli = path.join('node_modules', '.bin', 'cooking') + ` build -c ${paths} -p`;execSync(cli, {  stdio: 'inherit'});

以上方法主要是擷取所有組件名,然後拼接為 shell 命令,執行 shell 命令進行 build。

// build/bin/iconInit.js'use strict';var postcss = require('postcss');var fs = require('fs');var path = require('path');var fontFile = fs.readFileSync(path.resolve(__dirname, '../../packages/theme-chalk/src/icon.scss'), 'utf8');var nodes = postcss.parse(fontFile).nodes;var classList = [];// 遍曆匹配正則,符合則傳入到數組中nodes.forEach((node) => {  var selector = node.selector || '';  var reg = new RegExp(/\.el-icon-([^:]+):before/); // 正則: .el-icon-(多個非:字元):before  var arr = selector.match(reg);  if (arr && arr[1]) {    classList.push(arr[1]);  }});// 匯出 icon.json 檔案fs.writeFile(path.resolve(__dirname, '../../examples/icon.json'), JSON.stringify(classList));

以上方法通過解析 icon.scss 最終匯出 icon.json 檔案,該檔案儲存了各種表徵圖。

// build/bin/i18n.js'use strict';var fs = require('fs');var path = require('path');// 擷取 page.jsonvar langConfig = require('../../examples/i18n/page.json');langConfig.forEach(lang => {  try {    // 擷取檔案資訊    fs.statSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`));  } catch (e) {    // 建立檔案夾    fs.mkdirSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`));  }  // 遍曆寫入檔案  Object.keys(lang.pages).forEach(page => {    var templatePath = path.resolve(__dirname, `../../examples/pages/template/${ page }.tpl`);    var outputPath = path.resolve(__dirname, `../../examples/pages/${ lang.lang }/${ page }.vue`);    var content = fs.readFileSync(templatePath, 'utf8');    var pairs = lang.pages[page];    Object.keys(pairs).forEach(key => {      content = content.replace(new RegExp(`<%=\\s*${ key }\\s*>`, 'g'), pairs[key]);    });    fs.writeFileSync(outputPath, content);  });});

以上代碼是國際化的過程,最終將會在 examples/pages/ 目錄中產生不同語言的內容。國際化具體內容請參照 國際化。

// build/bin/version.jsvar fs = require('fs');var path = require('path');var version = process.env.VERSION || require('../../package.json').version;var content = { '1.4.13': '1.4', '2.0.11': '2.0', '2.1.0': '2.1' };if (!content[version]) content[version] = '2.2';fs.writeFileSync(path.resolve(__dirname, '../../examples/versions.json'), JSON.stringify(content));

擷取 version,定義了一個 content,如果目前的版本不在 content 中,那麼再添加一個版本資料。由於我學習的版本是 2.2.1,最終產生的結果是:

{"1.4.13":"1.4","2.0.11":"2.0","2.1.0":"2.1","2.2.1":"2.2"}

出現這幾個版本號碼的原因麼,看下官網就能發現端倪,應該是幾個重要的穩定版本。

來看下 build-entry.js 檔案

// build/bin/build-entry.jsvar Components = require('../../components.json'); // 組件資料var fs = require('fs'); // node檔案系統var render = require('json-templater/string');var uppercamelcase = require('uppercamelcase'); // 駝峰大小寫寫法var path = require('path'); // node路徑系統var endOfLine = require('os').EOL;// 匯出路徑var OUTPUT_PATH = path.join(__dirname, '../../src/index.js');// 匯入template、安裝組件template、主要templatevar 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.map(component => {    Vue.component(component.name, component);  });  Vue.use(Loading.directive);  const ELEMENT = {};  ELEMENT.size = opts.size || '';  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;  Vue.prototype.$ELEMENT = ELEMENT;};/* istanbul ignore if */if (typeof window !== 'undefined' && window.Vue) {  install(window.Vue);}module.exports = {  version: '{{version}}',  locale: locale.use,  i18n: locale.i18n,  install,  CollapseTransition,  Loading,{{list}}};module.exports.default = module.exports;`;delete Components.font;// 組件名var ComponentNames = Object.keys(Components);var includeComponentTemplate = [];var installTemplate = [];var listTemplate = [];// 遍曆組件名解析templateComponentNames.forEach(name => {  var componentName = uppercamelcase(name); // 駝峰命名  includeComponentTemplate.push(render(IMPORT_TEMPLATE, {    name: componentName,    package: name  }));  if (['Loading', 'MessageBox', 'Notification', 'Message'].indexOf(componentName) === -1) {    installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, {      name: componentName,      component: name    }));  }  if (componentName !== 'Loading') listTemplate.push(`  ${componentName}`);});// 主要templatevar 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);

以上代碼中,先是定義了三個 template,然後使用 render 方法來渲染這些 template。最後產生一個主要 template 匯出為檔案。render 函數中的第二個參數為 template 中 {{name}} 的資料。
這個 render 方法來自 json-templater 庫,這個庫可以將字串編譯為 js 代碼。 依賴關係

看看 element 都 depend 了些什麼。下面對 element 的依賴作了注釋。

  "dependencies": {    // 非同步驗證器    "async-validator": "~1.8.1",    // vue和jsx合并參數的文法轉譯器。    "babel-helper-vue-jsx-merge-props": "^2.0.0",    // 深入合并    "deepmerge": "^1.2.0",    // 滑鼠滾輪在多個瀏覽器之間的標準化。    "normalize-wheel": "^1.0.1",    // 方法的 Throttle/debounce。 https://www.npmjs.com/package/throttle-debounce    "throttle-debounce": "^1.0.1"  },  "peerDependencies": {    // vue核心源碼    "vue": "^2.5.2"  },  "devDependencies": {    // 一個託管的全文、數字和分面搜尋引擎,能夠在第一次按鍵時提供即時結果。    "algoliasearch": "^3.24.5",    // babel    "babel-cli": "^6.14.0",    "babel-core": "^6.14.0",    "babel-loader": "^6.2.5",    "babel-plugin-add-module-exports": "^0.2.1",    "babel-plugin-module-resolver": "^2.2.0",    "babel-plugin-syntax-jsx": "^6.8.0",    "babel-plugin-transform-vue-jsx": "^3.3.0",    "babel-preset-es2015": "^6.14.0",    // chai斷言庫    "chai": "^3.5.0",    // 為伺服器專門設計的核心jQuery的快速、靈活、精益的實現。    "cheerio": "^0.18.0",    // node fs工具    "chokidar": "^1.7.0",    // cooking前端構建工具    "cooking": "^1.5.4",    "cooking-lint": "0.1.3",    // 繼承vue2配置項的cooking外掛程式    "cooking-vue2": "^0.3.3",    // 複製webpack外掛程式    "copy-webpack-plugin": "^4.1.1",    // 代碼測試覆蓋率    "coveralls": "^2.11.14",    // 跨平台支援UNIX命令    "cp-cli": "^1.0.2",    // 運行在平台上設定和使用環境變數的指令碼。    "cross-env": "^3.1.3",    // css 載入器    "css-loader": "^0.28.7",    // es6 promise支援    "es6-promise": "^4.0.5",    // eslint文法檢測    "eslint": "4.14.0",    "eslint-config-elemefe": "0.1.1",    "eslint-loader": "^1.9.0",    "eslint-plugin-html": "^4.0.1",    "eslint-plugin-json": "^1.2.0",    "extract-text-webpack-plugin": "^3.0.1",    // 檔案載入和儲存    "file-loader": "^1.1.5",    "file-save": "^0.2.0",    // 將檔案發布到github的 gh-pages 分支    "gh-pages": "^0.11.0",    // gulp打包    "gulp": "^3.9.1",    "gulp-autoprefixer": "^4.0.0",    "gulp-cssmin": "^0.1.7",    "gulp-postcss": "^6.1.1",    "gulp-sass": "^3.1.0",    // js 高亮    "highlight.js": "^9.3.0",    // html載入器    "html-loader": "^0.5.1",    // html webpack外掛程式    "html-webpack-plugin": "^2.30.1",    // A Webpack loader for injecting code into modules via their dependencies    "inject-loader": "^3.0.1",    // isparta instrumenter loader for webpack,用於測試    "isparta-loader": "^2.0.0",    // json載入器    "json-loader": "^0.5.7",    // json和js的模板產生工具    "json-templater": "^1.0.4",    // karma測試庫    "karma": "^1.3.0",    "karma-chrome-launcher": "^2.2.0",    "karma-coverage": "^1.1.1",    "karma-mocha": "^1.2.0",    "karma-sinon-chai": "^1.2.4",    "karma-sourcemap-loader": "^0.3.7",    "karma-spec-reporter": "0.0.26",    "karma-webpack": "^1.8.0",    // 用於管理具有多個包的JavaScript項目的工具。    "lerna": "^2.0.0-beta.32",    // 類比時間工具    "lolex": "^1.5.1",    // markdown解析器    "markdown-it": "^6.1.1",    "markdown-it-anchor": "^2.5.0",    "markdown-it-container": "^2.0.0",    // mocha測試庫    "mocha": "^3.1.1",    // node.js 的 sass    "node-sass": "^4.5.3",    // 視差滾動 https://perspective.js.org/#/zh-cn/    "perspective.js": "^1.0.0",    // postcss    "postcss": "^5.1.2",    "postcss-loader": "0.11.1",    "postcss-salad": "^1.0.8",    // node深度刪除模組    "rimraf": "^2.5.4",    // sass載入器    "sass-loader": "^6.0.6",    // sinon測試架構    "sinon": "^1.17.6",    "sinon-chai": "^2.8.0",    // 樣式載入器    "style-loader": "^0.19.0",    // utf-8 字元轉換    "transliteration": "^1.1.11",    // 駝峰寫法    "uppercamelcase": "^1.1.0",    "url-loader": "^0.6.2",    // vue    "vue": "^2.5.2",    "vue-loader": "^13.3.0",    "vue-markdown-loader": "1",    "vue-router": "2.7.0",    "vue-template-compiler": "^2.5.2",    "vue-template-es2015-compiler": "^1.6.0",    // webpack    "webpack": "^3.7.1",    "webpack-dev-server": "^2.9.1",    "webpack-node-externals": "^1.6.0"  }

阿西吧,這依賴庫真心多~~不知道他們如何找到這麼多庫的。 src目錄

再來看看項目結構部分。按常理源碼肯定是放在 src 目錄中的,我們找到 src/index.js。代碼有點長,只貼出 install 方法部分了。說下都幹了什麼:匯入所有組件,定義安裝方法,判斷環境執行 install 方法,最後整體匯出。

const install = function(Vue, opts = {}) {  locale.use(opts.locale);  locale.i18n(opts.i18n);  components.map(component => {    // 遍曆將組件加入到Vue中    Vue.component(component.name, component);  });  // 載入中  Vue.use(Loading.directive);  const ELEMENT = {};  ELEMENT.size = opts.size || '';  // 定義Vue的原型 prototype  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;  Vue.prototype.$ELEMENT = ELEMENT;};/* istanbul ignore if */if (typeof window !== 'undefined' && window.Vue) {  install(window.Vue);}

從組件的匯入 import Button from '../packages/button/index.js'; 可以看到所有組件都是在 packages 目錄下的。這部分我們會在之後重點學習。
那麼問題來了,既然組件都在 packages 中了,那麼 src 目錄下都幹了些什麼呢。來看看各個目錄的功能: directive 實現滾輪最佳化和避免重複點擊。 locale 用於 i18n 國際化功能。 mixins 看樣子應該是用於混合到 Vue 執行個體的 options 中的。 transition 在渲染是操作style做過渡效果處理。 utils 工具檔案夾。

主要目的是項目結構,就不深入展開了。如有需要後面再講。 其他目錄

上面說過,package 目錄中存放了所有 component 組件的代碼。另外也存放了組件的樣式 .scss 檔案。
而對於type目錄中,存放的 .ts 檔案。都是 TypeScript 檔案。但是有個問題,我不太清楚這些 .ts 都用在何處。而且在 package.json 中也未匯入 TypeScript 的庫,只是在更新日誌中有 新增 TypeScript 型別宣告 這麼一句話。這點有所疑惑。
test 目錄下是各個組件的單元測試用例,這部分是學習單元測試寫法的很好的參考代碼(我學習測試架構就是在這裡學的)。需要學習單元測試的可以深入看看。
example 目錄下是 element 的樣本項目。我們的目的是學習源碼,所以這部分先忽略~ 最後

簡單瞭解了下項目的編譯、項目的依賴庫情況、項目的機構。下一篇開始學習一些組件的實現。逐步深入扒開element的神秘面紗。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.