1. 從 Element UI 源碼中學(xué)到的 5 個技巧

        共 13194字,需瀏覽 27分鐘

         ·

        2021-08-01 10:26


        導(dǎo)語Element UI[1] 是世界級最優(yōu)秀的UI框架之一。這個優(yōu)秀的框架有哪些我們能學(xué)習(xí)的優(yōu)點呢?這篇文章將分享作者在查看這個框架倉庫源碼中認(rèn)為值得參考的技巧,建議配合element[2]源碼食用更佳。

        技巧一:組件腳手架

        腳手架在創(chuàng)建新組件的應(yīng)用:規(guī)范代碼目錄,減少搬磚工作量 ,腳手架源碼實現(xiàn):build/bin/new.js[3]

         # 執(zhí)行命令,參數(shù)說明    
         # componentname 組件名 必填   
         # chineseName 組將中文名 選填 ,不填默認(rèn)取值componentname  
         # node build/bin/new.js componentname [chineseName] 命令說明
         
         #
         在element項目下執(zhí)行
         node build/bin/new.js helloworld 示例

        腳手架處理結(jié)果:

        • 1、組件樣式處理:

          1.1 生成組件樣式 packages/theme-chalk/src/${componentname}.scss

          1.2 樣式入口文件packages/theme-chalk/src/index.scss 導(dǎo)入改組件樣式

        • 2、組件代碼處理:

          2.1 生成組件代碼文件 :packages/componentname/index.js和packages/{componentname}/index.js和packages/componentname/index.js和packages/{componentname}/src/main.vue

          2.2 新增組件的路徑信息導(dǎo)入到 components.json ,該文件是json對象,存放了組件的名字和組件入口路徑

        • 3、生成組件文檔:

          3.1 生成examlpes/docs/{i18n}/component.md, 其中,i18n=['en-US','es','fr-FR','zh-CN']

          3.2 將新增的組件文檔的標(biāo)題和路徑添加到 examples/nav.config.json,該文件是Element UI的組件文檔的目錄,保存了組件文檔的標(biāo)題和路由

        • 4、生成單元測試:

          4.1 生成單元測試文件:test/unit/specs/component.spec.js

        • 5、生成組件接口定義:

          5.1 生成組件的描述文件:types/component.d.ts

          5.2在types/element-ui.d.ts新增新組件的接口定義

        一個優(yōu)秀的組件,除了需要要把組件的代碼寫好,還有有單元測試、文檔說明,最好,也有有接口定義(這編譯器就有友好的使用提示),Element的組件起步就已經(jīng)配齊了??

        技巧二:用代碼來生成代碼

        源碼入口文件生成:Element UI 目前一共有80個組件,如果要導(dǎo)出這80個組件,那么引入、導(dǎo)出和聲明Vue組件的代碼都要寫240次,而且,組件的增刪都要去修改入口文件。為了減少這部分工作量,基于components.json[4]來生成入口文件的組件引入和導(dǎo)出。

        components.json內(nèi)容如下:

        入口文件有三處代碼(引入,導(dǎo)出和聲名組件)都要重復(fù)了80次,下面以引入代碼語句作為說明示例:

        引入代碼示例

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

        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';
        import DropdownMenu from '../packages/dropdown-menu/index.js';
        import DropdownItem from '../packages/dropdown-item/index.js';
        import Menu from '../packages/menu/index.js';
        /**省略 70多個組件導(dǎo)入**/
        import CollapseTransition from 'element-ui/src/transitions/collapse-transition';

        生成上述代碼,主要邏輯:讀入components.json作為數(shù)據(jù),然后模版拼接起來,具體實現(xiàn)如下:


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

        // import 語句語法模版
        var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';

        var ComponentNames = Object.keys(Components);
        var includeComponentTemplate = [];

        ComponentNames.forEach(name => {
          var componentName = uppercamelcase(name);
          includeComponentTemplate.push(
              // 單條import生成語句
              render(IMPORT_TEMPLATE, {
                name: componentName,
                package: name
              })
          );
        });

        console.info(includeComponentTemplate.join(endOfLine))

        生成代碼的實現(xiàn):build/bin/build-entry.js[5]

        代碼位置:src/index.js[6]

        技巧三:用md去寫組件文檔和示例

        十分優(yōu)雅的文檔和示例書寫方式:文檔和示例統(tǒng)一寫到markdown文件中,在通過編寫md-loader,先轉(zhuǎn)成html文件,再轉(zhuǎn)成vue的組件,再渲染,這種做法筆者認(rèn)為是太有意思。

        先看一下 Element UI 的文檔頁面的框架:

        路由組件邏輯(詳細(xì)代碼[7]):

        // 截取組件文檔的路由代碼片段
        const LOAD_DOCS_MAP = {
          'zh-CN'path => {
            return r => require.ensure([], () =>
              r(require(`./docs/zh-CN${path}.md`)),
            'zh-CN');
          },
          'en-US'path => {
            return r => require.ensure([], () =>
              r(require(`./docs/en-US${path}.md`)),
            'en-US');
          },
          'es'path => {
            return r => require.ensure([], () =>
              r(require(`./docs/es${path}.md`)),
            'es');
          },
          'fr-FR'path => {
            return r => require.ensure([], () =>
              r(require(`./docs/fr-FR${path}.md`)),
            'fr-FR');
          }
        };

        注意到,路由加載的組件不 vue 組件,而是,一個 markdown 文件。這個過程,是在 webpack 打包過程中自定義一個 loader 來實現(xiàn):markdown 轉(zhuǎn)成 vue 來實現(xiàn)的。

        詳細(xì)實現(xiàn):build/md-loader/index.js[8]

        接下來,將詳細(xì)分享文檔如何實現(xiàn)組件演示的效果:

        第一步:擴(kuò)展了markdown的container格式:demo

        :::demo ${content}:::

        實現(xiàn)代碼:

        const md = require('markdown-it')();
        const fs = require('fs');
        const path = require('path');
        const mdContainer = require('markdown-it-container');

        module.exports = md => {
        md.use(mdContainer, 'demo', {
        validate(params) {
        return params.trim().match(/^demo\s*(.*)$/);
        },
        render(tokens, idx) {
        const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
        if (tokens[idx].nesting === 1) {
        const description = m && m.length > 1 ? m[1] : '';
        const content = tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : '';
        return `<demo-block>
        ${description ? `<div>${md.render(description)}</div>` : ''}
        <!--element-demo: ${content}:element-demo-->
        `;
        }
        return '</demo-block>';
        }
        });

        md.use(mdContainer, 'tip');
        md.use(mdContainer, 'warning');
        };

        const inputPath = path.resolve(__dirname, './alert.md');
        const outputPath = path.resolve(__dirname, './alert.html');

        const input = fs.readFileSync(inputPath, {encoding: 'utf8'});
        const ounput = md.render(input);
        fs.writeFileSync(outputPath, ounput, {encoding: 'utf8'});}

        執(zhí)行結(jié)果:

        提取注釋內(nèi)容轉(zhuǎn)成組件的實例實現(xiàn)

        第二步:將html轉(zhuǎn)成ComponentDoc.vue

         <template>    
           <section class="content element-doc">        
            <h2>Alert 警告</h2>        
            <p>用于頁面中展示重要的提示信息。</p>        
            <h3>自定義關(guān)閉按鈕</h3>        
            <p>自定義關(guān)閉按鈕為文字或其他符號。</p>  
            
            <!--示例展示 這個步是最關(guān)鍵-->    
            <demo-block>            
                <template name="source">              
                <element-demo0 />            
                </template>            
                <template name="default"></template>            
                <slot name="highlight"></slot>        
            </demo-block>  
            
          </section>
         </template>  

        組件:<demo-block>,源碼位置:examples/components/demo-block.vue[9]

        DemoBlockComponent效果展示如下圖:

        第三步:示例效果展示

        • 第一種,組件僅僅只有模板,沒有其他屬性,就跟描述內(nèi)容插槽一樣,直接以插槽透傳就行;
        • 第二種,是組件有script內(nèi)容,怎么處理呢?如下圖

        組件代碼是調(diào)用vue-template-compiler模塊生成的,參照:build/md-loader/util.js:L30[10]

        技巧四:icon組件示例

        Element UI 提供了280個icon,人工搬磚,寫文檔,又得加不少班呀。對于有追求的程序員,當(dāng)然要有想法了。處理技巧:使用postcss模塊解析icons的樣式文件,提取出el-icon-XXX的className,將所有icon的className組裝成數(shù)組中,保存到examples/icon.json[11]

        通過樣式class提取icon名字,代碼實現(xiàn)如下:

        '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');
        // 借助postcss解析css文件
        var nodes = postcss.parse(fontFile).nodes;
        var classList = [];

        // 通過正則獲取icon的name
        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 文件順序倒序排列
        console.info(classList.length);

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

        技巧三種中是將icons的icons.md生成的ComponentDoc.vue組件,無法編寫代碼來傳入icons數(shù)組,那就直接注入 Vue中原型鏈中

         // 文檔入口引入
         import Vue from 'vue'
         import icon from './icon.json';
         Vue.prototype.$icon = icon;

        icon文檔書寫

        圖標(biāo)集合
        <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>

        效果:element.eleme.io/#/zh-CN/com…[12]

        一下子解決這么多的重復(fù)工作??

        技巧五:文檔多語言

        文檔多語言,使用腳本生成每中語言,都單獨生成一個vue模板,和技巧二類似,詳細(xì)參考:build/bin/i18n.js[13]

        參考資料

        [1]

        https://element.eleme.io/: https://link.juejin.cn/?target=https%3A%2F%2Felement.eleme.io%2F

        [2]

        https://github.com/ElemeFE/element: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement

        [3]

        https://github.com/ElemeFE/element/blob/45c0ef46f298cc9dfbe72d20039795184a8ddab0/build/bin/new.js: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement%2Fblob%2F45c0ef46f298cc9dfbe72d20039795184a8ddab0%2Fbuild%2Fbin%2Fnew.js

        [4]

        https://github.com/ElemeFE/element/blob/45c0ef46f298cc9dfbe72d20039795184a8ddab0/components.json: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement%2Fblob%2F45c0ef46f298cc9dfbe72d20039795184a8ddab0%2Fcomponents.json

        [5]

        https://github.com/ElemeFE/element/blob/45c0ef46f298cc9dfbe72d20039795184a8ddab0/build/bin/build-entry.js: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement%2Fblob%2F45c0ef46f298cc9dfbe72d20039795184a8ddab0%2Fbuild%2Fbin%2Fbuild-entry.js

        [6]

        https://github.com/ElemeFE/element/blob/45c0ef46f298cc9dfbe72d20039795184a8ddab0/src/index.js: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement%2Fblob%2F45c0ef46f298cc9dfbe72d20039795184a8ddab0%2Fsrc%2Findex.js

        [7]

        https://github.com/ElemeFE/element/blob/dev/examples/route.config.js: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement%2Fblob%2Fdev%2Fexamples%2Froute.config.js

        [8]

        https://github.com/ElemeFE/element/blob/45c0ef46f298cc9dfbe72d20039795184a8ddab0/build/md-loader/index.js: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement%2Fblob%2F45c0ef46f298cc9dfbe72d20039795184a8ddab0%2Fbuild%2Fmd-loader%2Findex.js

        [9]

        https://github.com/ElemeFE/element/blob/45c0ef46f298cc9dfbe72d20039795184a8ddab0/examples/components/demo-block.vue: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement%2Fblob%2F45c0ef46f298cc9dfbe72d20039795184a8ddab0%2Fexamples%2Fcomponents%2Fdemo-block.vue

        [10]

        https://github.com/ElemeFE/element/blob/45c0ef46f298cc9dfbe72d20039795184a8ddab0/build/md-loader/util.js#L30: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement%2Fblob%2F45c0ef46f298cc9dfbe72d20039795184a8ddab0%2Fbuild%2Fmd-loader%2Futil.js%23L30

        [11]

        https://github.com/ElemeFE/element/blob/45c0ef46f298cc9dfbe72d20039795184a8ddab0/examples/icon.json: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement%2Fblob%2F45c0ef46f298cc9dfbe72d20039795184a8ddab0%2Fexamples%2Ficon.json

        [12]

        https://element.eleme.io/#/zh-CN/component/icon#tu-biao-ji-he: https://link.juejin.cn/?target=https%3A%2F%2Felement.eleme.io%2F%23%2Fzh-CN%2Fcomponent%2Ficon%23tu-biao-ji-he

        [13]

        https://github.com/ElemeFE/element/blob/45c0ef46f298cc9dfbe72d20039795184a8ddab0/build/bin/i18n.js: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FElemeFE%2Felement%2Fblob%2F45c0ef46f298cc9dfbe72d20039795184a8ddab0%2Fbuild%2Fbin%2Fi18n.js


        轉(zhuǎn)自:azuo

        https://juejin.cn/post/6966491047257964575


        瀏覽 51
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
          
          

            1. 91人人妻| 日本丝袜91av | 精品国产一区二 | 一区二区日韩国产 | 黄肉文小说 |