• 基于fis3的组件可视化道路


      首先说明一下,即使不熟悉fis3,阅读文本应该也会有所收获。

      本文以fis-parser-imweb-tplv2插件为模板插件,目的不在于使用哪个模板,而是组件可视化的实现思路,不必担心。

    先说说模板插件

      首先说明一下,我们的项目使用的fis3自带的mod.js做模块化开发。

      fis-parser-imweb-tplv2插件是同事在imweb待着的时候写的。模板使用和jsp写法一致,文件类型为tpl类型

    <div class="tips">
        <em>
          <i class="triangle"></i>
          <a class="close" href="javascript:void(0)" title="关闭"></a>
        </em>
        <strong><%=content%></strong>
        <a href=<%=linkUrl%> title=<%=linkTxt%> target="<%=target%>"> <%=linkTxt%> ></a>
    </div>

      实现源码也比较简单易懂。fis3的配置

        .match(//(.+).tpl$/, { // js 模版一律用 .tpl 作为后缀
            isMod: true,
            rExt: 'js',
            id: '$1.tpl',
            url: '$0.tpl',
            moduleId: '$1.tpl',
            release: '$0.tpl', // 发布的后的文件名,避免和同目录下的 js 冲突
            parser: fis.plugin('imweb-tplv2')
        })

      最终生成的模块化的.tpl.js文件如下

    define('common/module/rightsideBar/rightsideBar.tpl', function(require, exports, module) {
    
      return  function (it, opt) {
          it = it || {};
          with(it) {
              var _$out_= [];
              _$out_.push('<div class="right-sidebar"><a href="javascript:void(0)" class="right-sidebar-unregistered-close"></a><a id="rsidereg" href="/register.html?channel=pcgwzc3" class="right-sidebar-unregistered" style="visibility:', (isLogin ?'hidden' :'visible'), '"><span class="right-sidebar-unregistered-content"></span></a><a href="http://wpa.b.qq.com/cgi/wpa.php?ln=2&amp;uin=4008225538" class="right-sidebar-item right-sidebar-item3" target="_blank"><span class="right-sidebar-item1-content"></span></a><a href="javascript:void(0)" class="right-sidebar-item right-sidebar-item2"><span class="right-sidebar-item2-content"></span><div class="right-sidebar-item-tip--code"> <img src="', '/static/pc-dev/common/module/topBar/top_wx.png', '"> <i class="right-sidebar-item-tip--code-tri"><i></i></i></div></a><a href="javascript:void(0)" class="right-sidebar-item right-sidebar-item3"><span class="right-sidebar-item3-content"></span><div class="right-sidebar-item-tip--code"> <img src="', '/static/pc-dev/common/module/topBar/top_app.png', '"> <i class="right-sidebar-item-tip--code-tri"><i></i></i></div></a><a href="javascript:void(0)" id="rjsq" class="right-sidebar-item right-sidebar-item4" target="_blank"><span class="right-sidebar-item4-content"></span></a><a href="javascript:window.scrollTo(0,0)" class="right-sidebar-item right-sidebar-item5" style="display: none;"><span class="right-sidebar-item5-content"></span></a></div>');
            return _$out_.join('');
          }
      }
    
    });

      fis3打包前和打包后的文件结构更改

      

      使用也比较简单

    var tpl_rightsideBar = require('rightsideBar.tpl');
    tpl_rightsideBar(opt);//opt是需要传递进去的对象。详细查看rightsideBar.tpl.js的源码对应的it参数

      当然,我们封装一个组件不可能直接去使用这个tpl。而是提供一个外部组件函数,然后传递参数到这个组件函数中,组件函数不仅渲染页面(即插入组件dom),还处理相关的逻辑。如上面的rightsideBar.js就是为外部提供一个组件函数。rightsideBar组件比较简单,只需要传递一个父节点即可,不需要其他外部数据来做处理。部分源码

    /**
     * @author chua
     * @date 2016-5-9
     * @description 首页右侧导航栏组件,依赖模版 rightsideBar.tpl,rightsideBar.scss;
    
     * @实例化:rightsideBar = new rightsideBar(dom, datas);
     * @param dom {Dom} 为头部组件父级节点,将根据情况append模版,生成头部节点;
     * @param datas {json} 初始化组件的数据,数据格式如下
     */
    
    /*
     * @require './rightsideBar.scss';
     */
    var tpl_rightsideBar = require('./rightsideBar.tpl');
    function rightsideBar(cont,opt) {
        this.cont = $(cont);
        this.opt = opt;
        this.init();
    };
    rightsideBar.prototype.renderHTML = function() {
        //渲染前处理...
        this.cont.empty().append(tpl_rightsideBar(this.opt));
    };
    rightsideBar.prototype.bindEvent = function() {
        //绑定事件
        this.cont.on('click', '.xxx', function() {
            //处理内容...
        });
    };
    rightsideBar.prototype.init = function() {
        this.renderHTML();
        this.bindEvent();
    }
    return rightsideBar;

      rightsideBar.js会被当做模块来编译(我们在fis-conf.js中配置的),最终编译出来后外面会被define包裹。

    define('common/module/rightsideBar/rightsideBar', function(require, exports, module) {
        //代码正文
        ...
    
      rightsideBar.prototype.init = function() {
          this.renderHTML();
          this.bindEvent();
      }
      
      return rightsideBar;
    
    });

       使用方法也比较简单

           html:
           <div class="js-rightsideBar"></div>
      
          js:
          var rightsideBar = require('/common/module/rightsideBar/rightsideBar.js');
          new rightsideBar($('.js-rightsideBar'));

      而我们的所有组件都放在一个组件文件夹module中,每一个组件一个文件夹(当然页可以是多个类似的组件放在一起,公用资源),文件夹下面放置所有这个组件相关的资源。如上图rightsideBar这个组件。

      那么如何做组件可视化?有几个过程是必须要做的

      1.需要遍历module中所有的组件(即遍历每一个组件下面的.js文件,一个组件文件夹下有多个js文件,表明这个组件文件夹下有多个组件,只是为了公用组件资源才放在了同一个组件下),提取出其中的使用样例(从注释代码中提取,所以必须要规定这段demo代码的规则)。

      2.必须在每一个组件的注释中写demo代码。

      3.将提取的demo代码写入指定的组件可视化文件中,随fis编译成目标文件(最终在网上打开这个文件就能预览各个组件的demo了)。

      在上面的基础上,改如何实现组价的可视化?

    第一阶段的组件可视化

      第一阶段的组件可视化使用node+fis的方式实现。原理是在fis编译之前使用node执行一个脚本,这个脚本完成遍历组件、提取demo代码、生成组件可视化目标文件。然后在使用fis编译打包,启动服务后在网上访问即可。之所以第一阶段这么做的原因有两点:鄙人比较熟悉node但是对fis插件编写不太熟悉,不敢确定使用fis插件方式是否可行;其次上头希望能在短期内能看到一定效果。先看一下工程结构目录,v_components文件夹里包含了所有用来生成组件可视化文件的工具文件,node执行脚本为index.js

      

      工程源文件点击这里

      实现步骤:

      1.规定文件注释代码中"@example"和"@example end"之间的字符串被认为是组件demo代码。

    /**
       * @author chua
       * @date 2016-5-9
       * @description 首页右侧导航栏组件,依赖模版 rightsideBar.tpl,rightsideBar.scss;基于jQuery和jquery.cookie.js
      
       * @实例化:rightsideBar = new rightsideBar(dom, datas);
       * @param dom {Dom} 为头部组件父级节点,将根据情况append模版,生成头部节点;
       * @param datas {json} 初始化组件的数据,数据格式如下
      
       *
       * @example
           html:
           <div class="js-rightsideBar"></div>
      
          js:
           var rightsideBar = require('/common/module/rightsideBar/rightsideBar.js');
          new rightsideBar($('.js-rightsideBar'));
      
          @example end
       */

       其中html:后面跟着的是html代码,js:后面跟着的是js执行代码。注意不要出现不符合代码格式的字符,"html:"、"js:"分别为html代码段和js代码段开始的标志。其后的代码分别要严格按照html和js的格式要求书写

      

      2.为了简化和配置更加灵活。我添加了config.json和wrap.html两个文件来配合主文件index.js文件。

      

      其中index.js文件作用是作为node脚本运行,最终生成最新的v_components.html。v_components.css和v_components.js是给v_component.html使用的,毕竟组件可视化需要一些展示和交互

      config.json的作用是希望能够配置组件的一些属性,让其更灵活,移植性更加。目前只支持一个配置:组件的目录 (建议使用相对路径,否则在index.js中可能找不到)

    {
        "modulePath": "../common/module/"
    }

      warp.html是用来生成v_components.html的模板文件。里面包含了v_components.html文件需要的样式文件和脚本文件。这个文件也是根据实际情况配置

    <!DOCTYPE html>
    <html>
    <head>
        <title>组件可视化</title>
        <link rel="import" href="/common/html/meta.html?__inline">
        <link rel="stylesheet" type="text/css" href="/common/css/common.scss" />
        <link rel="stylesheet" type="text/css" href="v_components.css" />
        <script type="text/javascript" src="/common/dep/mod.js" data-loader></script>
        <script type="text/javascript" src="/common/dep/jquery.js" data-loader></script>
        <script type="text/javascript" src="/common/js/common.js" data-loader></script>
        <script type="text/javascript" src="v_components.js" data-loader></script>
    </head>
    <body>    
    </body>
    </html>

      3.主程序index.js读取配置文件config.json中配置的组件目录遍历每一个组件(里面的js文件),提取注释中的demo代码,以wrap.html为模板,将html代码插入为节点,将js脚本插入到script节点中。

    var fs = require('fs');
    ...
    var writerStream = fs.createWriteStream('v_components.html');
    var regs = {
        'wraphtml': /^([sS]*<(body)>[sS]*)(</2>[sS]*)$/,
        //懒惰匹配到第一个*/
        'example': //**([sS]*)@example[sS]*?html:([sS]*?)js:([sS]*?)@example end([s|S]*?)*//,//懒惰匹配第一个*/
        ...
    };
    
    fs.readFile('config.json', function(err, data){
        ...
        root = obj.modulePath;
        if(datas){
            loopModule();
        }    
    })
    fs.readFile('wrap.html', function(err, data){                        
        ...
        datas = data.toString();
        if(root){
            loopModule();
        }    
    });
    //遍历所有模块文件
    function loopModule(){
        fs.readdir(root, function(err, ffiles){
            ...
            //遍历所有组件
            ffiles.forEach(function(ffile){
                ...
                fs.readdir(root + ffile, function (err, files){
                    ...
                    files.forEach(function(file){//处理每一个模块文件中的模块并找到组件主文件(js文件)
                        if(regs.jsfile.test(file)){
                            ...
                            fs.readFile(pa, function(err, data){
                                ...
                                if(match = data.toString().match(regs.example)){//匹配demo代码段
                                    //截取相关信息并添加相关节点
                                    ...
                                    innerRightT += '<div class="right-side-top-item' + (count == 0? ' visible': '') + '" data-mod="'+ file +'">' + match[2] + '</div>';
                                    innerJs += match[3] + ";";//js脚本都放在innerJs中
                                    count++;
                                }
                                
                                if(subModLeng == 0){//处理完所有子模块才能说明处理完了整个模块文件夹
                                    unDomoduleLength--;
                                }                            
                                if(unDomoduleLength == 0){//处理完所有的模块后,最后写入文件
                                    var innerBody = warpText(innerLeftWrap, innerLeft) 
                                        + warpText(innerRightWrap, warpText(innerRightTWrap, innerRightT) + warpText(innerRightBWrap, innerRightB))
                                        + warpText(jsWrap, innerJs);
    
                                    //使用utf8编码写入数据
                                    writerStream.write(datas.replace(regs.wraphtml, '$1' + innerBody + '$3'), 'UTF8');
                                    //标记文件结尾
                                    writerStream.end();
                                }                                
                            });
                        }
                        
                    })
                });
            })
        })
    }
    //用数组wrapArr包裹inner并返回包裹结果
    function warpText(wrapArr, inner){...}
    //将str字符串转换成HTML格式
    function transToHtml(str){...}
    View Code

      最终在v_components目录下执行:node index

      生成v_components.html文件

      在pc目录下fis3编译:fis3 release dev

      在和pc同级的目录下面生成pc-dev文件夹

      在pc-dev目录下执行:node server

      打开浏览器访问:http://localhost:3000/v_components/v_components.html

      

      左侧是所有的组件的列表,点击之能看到每一个组件的展示效果。我很丑,但是我很有内涵

    第二阶段的组件可视化——fis3插件

      之前说过要将组件可视化做成fis插件,随fis编译一起编译打包,不用在手动执行node生成相应文件。主要面临的问题是:如何让组件更加通用?这里面几个需要考虑的点

      1.如何从组件代码的注释中提取出demo代码段。

      这里我使用了下面的正则来匹配

    //**([sS]*)@example[sS]*?(html:([sS]*?)js:([sS]*?))@example end([s|S]*?)*//,//懒惰匹配第一个*/

      匹配文件注释中‘@example’和‘@example end’之间的代码。如

    /**
       * @example
           html:
           <div class="js-rightsideBar"></div>
      
          js:
           var rightsideBar = require('/common/module/rightsideBar/rightsideBar.js');
          new rightsideBar($('.js-rightsideBar'));
      
          @example end
       */

      这个部分本来想做成可以配置的,但是觉得没有太大的意义,就默认使用这个正则来匹配组件中的demo代码。

      2.插件需要那些参数,是的插件更加灵活

      下面是最终确定下来的参数(路径都配置绝对路径)

                wrap: '/v_components/wrap.html',//组件可视化原型文件,用来包裹组件可视化代码
                url: '/v_components.html', //目标文件
                COMPath: '/common/module',//组件集合目录
                moduleListInstead: 'instead of modules',//使用模块列表节点替换当前文本
                moduleViewInstead: 'instead of view htmls',//使用模块视图列表节点替换当前文本
                moduleCommentsInstead: 'instead of commnets',//使用模块注释列表节点替换当前文本
                moduleJsInstead: 'instead of js'//使用js脚本节点替换当前文本

      当前文件的目录结构

      

      其中wrap对应的文件wrap.html作用非常重要。后面四个参数moduleXXXInstead对应的值必须在wrap.html中能够找到,然后使用插件拼装好的数据来替换他。wrap.html源码如下

    <!DOCTYPE html>
    <html>
    <head>
        <title>组件可视化</title>
        ...
    </head>
    <body>    
    <div class="left-side">instead of modules</div>
    <div class="right-side">
        <div class="right-side-top">instead of view htmls</div>
        <div class="right-side-bottom">instead of commnets</div>
    </div>
    ...
    <script type="text/javascript">instead of js</script>
    </body>
    </html>

       最终执行fis后上面黑色粗体文字全部被替换

    <!DOCTYPE html>
    <html>
    <head>
        <title>组件可视化</title>
        ...
    </head>
    <body>    
    <div class="left-side"><div data-mod="financialsBar">financialsBar</div><div data-mod="financialsSmlBar">financialsSmlBar</div><div data-mod="rightsideBar">rightsideBar</div></div>
    <div class="right-side">
        <div class="right-side-top"><div data-mod="financialsBar">
          <!-- 新手 -->
          <div class="js-financialsBar1"></div>
          <!-- 活期 -->
          <div class="js-financialsBar2"></div>
          <!-- 定期 -->
          <div class="js-financialsBar3"></div>
      
          </div><div data-mod="financialsSmlBar">
          <!-- 定期理财 -->
          <div class="js-financialsSmlBar1"></div>
          <!-- 债权转让 -->
          <div class="js-financialsSmlBar2"></div>
          </div><div data-mod="rightsideBar">
           <div class="js-rightsideBar"></div>
      
          </div></div>
        <div class="right-side-bottom"><div data-mod="financialsBar"><div >样例:<div>html:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!--&nbsp;新手&nbsp;--&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;div&nbsp;class="js-financialsBar1"&gt;&lt;/div&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!--&nbsp;活期&nbsp;--&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;div&nbsp;class="js-financialsBar2"&gt;&lt;/div......</div>
    </div>
    ...
    <script type="text/javascript">var financialsBar = require('common/module/financialsBar/financialsBar');
          new financialsBar($('.js-financialsBar1'), {
              "type": "novice",
              "bdid": 1,
              "name": "农优宝A1231213",
              "title": "新手专享",
              "term": 1,
              "annualrate": 0.09,
              "interestRaise": 0.005,
              "surplusAmount": 30000,
              "projectScale": 50000,
              "status": "RZZ",
              "packStatus": "QGZ",
              "releaseDate": 425132121,
              "nowDate": 45641231321,
              "surplusBuyCount":1,
              'productType': 'NYB',
              'delayTime': 0
          });
          ...
        </script>
    </body>
    </html>
    View Code

       来自相同的组件的DOM节点的属性值data-mod相同

      fis-conf.js的配置片段

        .match('::package', {
            prepackager: fis.plugin('component-preview',{
                wrap: '/v_components/wrap.html',//组件可视化原型文件,用来包裹组件可视化代码
                url: '/v_components.html', //目标文件
                COMPath: '/common/module',//组件集合目录
                moduleListInstead: 'instead of modules',//使用模块列表节点替换当前文本
                moduleViewInstead: 'instead of view htmls',//使用模块视图列表节点替换当前文本
                moduleCommentsInstead: 'instead of commnets',//使用模块注释列表节点替换当前文本
                moduleJsInstead: 'instead of js'//使用js脚本节点替换当前文本
            })
        })

      更详细的demo查看fis3_component_preview_demo

      最终效果同第一阶段的组件可视化效果一样     

      实现原理:

      

      完整的插件源码查看fis3-prepackager-component-preview

       

  • 相关阅读:
    JDK1.5新特性
    mysql的基本使用
    IO简单示例
    序列化
    策略模式
    div+css布局之流体浮动布局
    xp优化
    Junit所使用的设计模式
    SSH使用总结(annotation配置方式)
    hibernate3.6.0使用总结
  • 原文地址:https://www.cnblogs.com/chuaWeb/p/component_preview.html
Copyright © 2020-2023  润新知