echarts與TmodJS的衝突 -- 模塊化加載器之間的衝突

  前些日子寫了一篇關於騰訊模板引擎TmodJS的文章《模板引擎artTemplate及模板預編譯器TmodJS的使用入門》,算是對其原理與使用進行了初步的接觸與研究。近期在一個項目中對TmodJS進行了嘗試,不曾想無心中竟然發現了TmodJS與百度圖表神器Echarts之間存在一個看似不能共存的衝突。html

  問題是這樣的:項目的某個頁面中同時顯式地引入了Echarts的主文件「echarts.js」以及TmodJS的依賴文件「require.js」。當執行到TmodJS所需的require()方法時,腳本會報「Error: [REQUIRE_FATAL]Relative ID is not allowed in global require」的錯誤,進而致使模板沒法被加載以及渲染。報錯如圖所示:數組

  

  從報錯信息上看,問題存在於echarts以及require方法上。這個項目中的Echarts均採用模塊化方式進行加載,而TmodJS也須要經過「require.js」來實現編譯後模板的加載以及後續的DOM渲染。但前者內置的是百度本身實現的模塊化加載器「esl」,由於此前對echarts的實現有着一些粗淺的瞭解,所以我認爲問題頗有可能和esl拋出的require方法與require.js拋出的同名方法不兼容相關。瀏覽器

  因爲頁面中先引入了esl,其在加載成功以後便在window對象中留下了全局方法require和define。而隨後引入的require.js在加載時因爲檢測到了上述兩個方法的存在,則認爲自身已有實例存在而不會繼續進行加載(若繼續加載則會覆蓋esl的兩個同名方法)。require.js的相關代碼以下:echarts

  雖然二者拋出方法的名稱一致,但它們所需參數的內容卻並不相同。二者require方法所需的第一個參數均爲array類型,但require.js須要的array中直接存放的就是模塊相對路徑的字串。而esl須要的參數更爲複雜一些,其先要經過require.config方法,將相對路徑的字串設置到一個object中的一個名爲paths的object的指定key中,然後在使用其本身定義的require時,會將以前指定的key依次壓入array中並將這個array做爲第一個參數。二者在require方法所需參數上的差別,最終致使了上述腳本錯誤的出現。看似兩個風馬牛不相及的東西,竟然由於模塊化加載器之間的衝突產生了交集,確實讓人小意外了一下。模塊化

  既然問題的緣由已經肯定,那麼咱們就能夠嘗試進行解決了。個人解決思路是,默認在頁面中不顯式引入require.js。在TmodJS須要使用require方法以前,先對當前瀏覽器環境的require進行斷定。若是加載了esl,則將模塊相對路徑按照esl須要的方式配置、轉化,進而經過esl加載。若是已存在自身或其它amd加載器拋出的require方法,則直接調用。若是乾脆就不存在require方法,則動態地加載require.js。具體代碼以下:函數

  // 代碼簡陋,還請各位路過的高手見諒。若有疏漏還請海涵、指正

  function NeedReq(arg){ // 參數形如{
                                  list:{  // 指定模塊的名稱和相對路徑
                                        'tmpl':'../../js/template'
                                  },
                                  callback:function(template){ // 指定require方法加載以後的回調
                                        // some code...
                                  },
                                  reqPath:'../../js/require.js'  // 指定require.js的相對路徑
                             }ui

    //  description: 智能判斷頁面是否加載了esl並由此決定是否加載require.js
    //  author: Liyanyang(gudaozr)
    //  version: 1.1spa

      var reqPath = [], reqRelativePath = [], reqCallback = null;  // 分別定義esl、require.js所用參數數組以及require.js動態加載成功後製定的回調函數
      var objIsEmpty = function(obj){  // 檢測對象是否爲空的函數,這裏主要用於判斷傳進來的參數是否有效
          if(typeof obj != 'object'){
              return false;
          }
          for(var key in obj){
              return false;
          }
          return true;
      };

      if(!arguments.length){ // 參數有效性、完整性判斷,不符合直接pass~
          return false;
      }else if(!arg.list || objIsEmpty(arg.list)){
          return false;
      }else if(typeof arg.callback != 'function'){
          return false;
      }else if(!arg.reqPath){
          return false;
      }

      var i = 0;
      for(var key in arg.list){ // 按照esl和require.js需求生成不一樣內容形式的參數數組
          reqPath[i] = key;
          reqRelativePath[i++] = arg.list[key];
      }

      reqCallback = arg.callback;

      if(window.require && window.require.loader == 'esl'){       // esl已經加載,不須要require.js
          require.config({
              paths:arg.list
          });
          require(reqPath,reqCallback);
      }else if(!window.require){      // esl和require.js均未加載,須要加載require.js
          var scrNode = document.createElement('script');
          scrNode.setAttribute('src',arg.reqPath);
          var scripts = document.getElementsByTagName('script');
          if(scripts.length){
              var scrParent = scripts[0].parentNode;
              if(scripts.length == 1){
                  scrParent.insertBefore(scrNode,scripts[0]);
              }else{
                  scrParent.insertBefore(scrNode,scripts[1]);
              }
              scrNode.onload = function(){
                  require(reqRelativePath,reqCallback);
              }
          }
      }else if(window.require && define.amd){     //  require.js或者其它amd加載器已經加載
          require(reqRelativePath,reqCallback);
      }

    return true;

}code

  寥寥草草記錄了下這個問題的發現以及解決,真心但願可以幫到遇到同類問題的朋友。若是文章中存在什麼錯誤或者不周之處,還請各位高手批評、指正。期待與你們進行交流!htm

相關文章
相關標籤/搜索